| Author | DaveJarvis <email> |
|---|---|
| Date | 2021-12-31 20:48:36 GMT-0800 |
| Commit | abc7d8fba2067fe9bb1ab175bf44bc6282e1c4ee |
| Parent | 4c15564 |
| Delta | 65 lines added, 289 lines removed, 224-line decrease |
| +/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | ||
| +package com.keenwrite.sigils; | ||
| + | ||
| +import org.junit.jupiter.api.Test; | ||
| + | ||
| +import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| + | ||
| +/** | ||
| + * Responsible for simulating R variable injection. | ||
| + */ | ||
| +class RKeyOperatorTest { | ||
| + | ||
| + /** | ||
| + * Test that a key name becomes an R variable. | ||
| + */ | ||
| + @Test | ||
| + void test_Process_KeyName_Processed() { | ||
| + final var mOperator = new RKeyOperator(); | ||
| + final var expected = "v$a$b$c$d"; | ||
| + final var actual = mOperator.apply( "a.b.c.d" ); | ||
| + | ||
| + assertEquals( expected, actual ); | ||
| + } | ||
| +} | ||
| -/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | ||
| -package com.keenwrite.sigils; | ||
| - | ||
| -import org.junit.jupiter.api.Test; | ||
| - | ||
| -import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| - | ||
| -/** | ||
| - * Responsible for simulating R variable injection. | ||
| - */ | ||
| -class RSigilOperatorTest { | ||
| - | ||
| - private final SigilOperator mOperator = createRSigilOperator(); | ||
| - | ||
| - /** | ||
| - * Test that a key name becomes an R variable. | ||
| - */ | ||
| - @Test | ||
| - void test_Entoken_KeyName_Tokenized() { | ||
| - final var expected = "v$a$b$c$d"; | ||
| - final var actual = mOperator.entoken( "{{a.b.c.d}}" ); | ||
| - assertEquals( expected, actual ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Test that a key name becomes a viable R expression. | ||
| - */ | ||
| - @Test | ||
| - void test_Apply_KeyName_Expression() { | ||
| - final var expected = "`r#x(v$a$b$c$d)`"; | ||
| - final var actual = mOperator.apply( "v$a$b$c$d" ); | ||
| - assertEquals( expected, actual ); | ||
| - } | ||
| - | ||
| - private Sigils createRSigils() { | ||
| - return createSigils( "x(", ")" ); | ||
| - } | ||
| - | ||
| - private Sigils createYamlSigils() { | ||
| - return createSigils( "{{", "}}" ); | ||
| - } | ||
| - | ||
| - private Sigils createSigils( final String began, final String ended ) { | ||
| - return new Sigils( began, ended ); | ||
| - } | ||
| - | ||
| - private YamlSigilOperator createYamlSigilOperator() { | ||
| - return new YamlSigilOperator( createYamlSigils() ); | ||
| - } | ||
| - | ||
| - private RSigilOperator createRSigilOperator() { | ||
| - return new RSigilOperator( createRSigils(), createYamlSigilOperator() ); | ||
| - } | ||
| -} | ||
| -/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | ||
| -package com.keenwrite.sigils; | ||
| - | ||
| -/** | ||
| - * Brackets variable names between {@link #PREFIX} and {@link #SUFFIX} sigils. | ||
| - */ | ||
| -public final class RSigilOperator extends SigilOperator { | ||
| - public static final String PREFIX = "`r#"; | ||
| - public static final char SUFFIX = '`'; | ||
| - | ||
| - private static final char KEY_SEPARATOR_DEF = '.'; | ||
| - private static final char KEY_SEPARATOR_R = '$'; | ||
| - | ||
| - /** | ||
| - * Definition variables are inserted into the document before R variables, | ||
| - * so this is required to reformat the definition variable suitable for R. | ||
| - */ | ||
| - private final SigilOperator mAntecedent; | ||
| - | ||
| - /** | ||
| - * Constructs a new {@link RSigilOperator} capable of wrapping tokens around | ||
| - * variable names (keys). | ||
| - * | ||
| - * @param sigils The starting and ending tokens. | ||
| - * @param antecedent The operator to use to undo any previous entokenizing. | ||
| - */ | ||
| - public RSigilOperator( final Sigils sigils, final SigilOperator antecedent ) { | ||
| - super( sigils ); | ||
| - | ||
| - mAntecedent = antecedent; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the given string with backticks prepended and appended. The | ||
| - * | ||
| - * @param key The string to adorn with R token delimiters. | ||
| - * @return PREFIX + delimiterBegan + variableName + delimiterEnded + SUFFIX. | ||
| - */ | ||
| - @Override | ||
| - public String apply( final String key ) { | ||
| - assert key != null; | ||
| - | ||
| - return PREFIX + getBegan() + key + getEnded() + SUFFIX; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Transforms a definition key (bracketed by token delimiters) into the | ||
| - * expected format for an R variable key name. | ||
| - * <p> | ||
| - * The algorithm to entoken a definition name is faster than | ||
| - * {@link String#replace(char, char)}. Faster still would be to cache the | ||
| - * values, but that would mean managing the cache when the user changes | ||
| - * the beginning and ending of the R delimiters. This code gives about a | ||
| - * 2% performance boost when scrolling using cursor keys. After the JIT | ||
| - * warms up, this super-minor bottleneck vanishes. | ||
| - * </p> | ||
| - * | ||
| - * @param key The variable name to transform, can be empty but not null. | ||
| - * @return The transformed variable name. | ||
| - */ | ||
| - public String entoken( final String key ) { | ||
| - final var detokened = new StringBuilder( key.length() ); | ||
| - | ||
| - detokened.append( "v$" ); | ||
| - detokened.append( mAntecedent.detoken( key ) ); | ||
| - | ||
| - // The 3 is for "v$X" where X cannot be a period. | ||
| - for( int i = detokened.length() - 1; i >= 3; i-- ) { | ||
| - if( detokened.charAt( i ) == KEY_SEPARATOR_DEF ) { | ||
| - detokened.setCharAt( i, KEY_SEPARATOR_R ); | ||
| - } | ||
| - } | ||
| - | ||
| - return detokened.toString(); | ||
| - } | ||
| -} | ||
| +/* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */ | ||
| +package com.keenwrite.sigils; | ||
| + | ||
| +import java.util.function.UnaryOperator; | ||
| +import java.util.regex.Matcher; | ||
| +import java.util.regex.Pattern; | ||
| + | ||
| +import static java.lang.String.format; | ||
| +import static java.util.regex.Pattern.compile; | ||
| +import static java.util.regex.Pattern.quote; | ||
| + | ||
| +/** | ||
| + * Responsible for bracketing definition keys with token delimiters. | ||
| + */ | ||
| +public class SigilKeyOperator implements UnaryOperator<String> { | ||
| + private final String mBegan; | ||
| + private final String mEnded; | ||
| + private final Pattern mPattern; | ||
| + | ||
| + public SigilKeyOperator( final String began, final String ended ) { | ||
| + assert began != null; | ||
| + assert ended != null; | ||
| + | ||
| + mBegan = began; | ||
| + mEnded = ended; | ||
| + mPattern = compile( format( "%s(.*?)%s", quote( began ), quote( ended ) ) ); | ||
| + } | ||
| + | ||
| + @Override | ||
| + public String apply( final String key ) { | ||
| + assert key != null; | ||
| + assert !key.startsWith( mBegan ); | ||
| + assert !key.endsWith( mEnded ); | ||
| + | ||
| + return mBegan + key + mEnded; | ||
| + } | ||
| + | ||
| + public Matcher match( final String text ) { | ||
| + return mPattern.matcher( text ); | ||
| + } | ||
| +} | ||
| -/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | ||
| -package com.keenwrite.sigils; | ||
| - | ||
| -import java.util.function.UnaryOperator; | ||
| - | ||
| -/** | ||
| - * Responsible for updating definition keys to use a machine-readable format | ||
| - * corresponding to the type of file being edited. This changes a definition | ||
| - * key name based on some criteria determined by the factory that creates | ||
| - * implementations of this interface. | ||
| - */ | ||
| -public class SigilOperator implements UnaryOperator<String> { | ||
| - private final Sigils mSigils; | ||
| - | ||
| - /** | ||
| - * Defines a new {@link SigilOperator} with the given sigils. | ||
| - * | ||
| - * @param began The sigil that denotes the start of a variable name. | ||
| - * @param ended The sigil that denotes the end of a variable name. | ||
| - */ | ||
| - public SigilOperator( final String began, final String ended ) { | ||
| - this( new Sigils( began, ended ) ); | ||
| - } | ||
| - | ||
| - SigilOperator( final Sigils sigils ) { | ||
| - assert sigils != null; | ||
| - | ||
| - mSigils = sigils; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the given {@link String} verbatim. Different implementations | ||
| - * can override to inject custom behaviours. | ||
| - * | ||
| - * @param key Returned verbatim. | ||
| - */ | ||
| - @Override | ||
| - public String apply( final String key ) { | ||
| - return key; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Wraps the given key in the began and ended tokens. This may perform any | ||
| - * preprocessing necessary to ensure the transformation happens. | ||
| - * | ||
| - * @param key The variable name to transform. | ||
| - * @return The given key with before/after sigils to delimit the key name. | ||
| - */ | ||
| - public String entoken( final String key ) { | ||
| - assert key != null; | ||
| - assert !key.startsWith( getBegan() ); | ||
| - assert !key.endsWith( getEnded() ); | ||
| - | ||
| - return getBegan() + key + getEnded(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Removes start and stop definition key delimiters from the given key. This | ||
| - * method does not check for delimiters, only that there are sufficient | ||
| - * characters to remove from either end of the given key. | ||
| - * | ||
| - * @param key The key adorned with start and stop tokens. | ||
| - * @return The given key with the delimiters removed. | ||
| - */ | ||
| - public String detoken( final String key ) { | ||
| - return key; | ||
| - } | ||
| - | ||
| - public Sigils getSigils() { | ||
| - return mSigils; | ||
| - } | ||
| - | ||
| - String getBegan() { | ||
| - return mSigils.getBegan(); | ||
| - } | ||
| - | ||
| - String getEnded() { | ||
| - return mSigils.getEnded(); | ||
| - } | ||
| - | ||
| - @Override | ||
| - public String toString() { | ||
| - return mSigils.toString(); | ||
| - } | ||
| -} | ||
| -/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | ||
| -package com.keenwrite.sigils; | ||
| - | ||
| -import java.util.AbstractMap.SimpleImmutableEntry; | ||
| - | ||
| -/** | ||
| - * Convenience class for pairing a start and an end sigil together. | ||
| - */ | ||
| -public final class Sigils extends SimpleImmutableEntry<String, String> { | ||
| - | ||
| - /** | ||
| - * Associates a new key-value pair. | ||
| - * | ||
| - * @param began The starting sigil. | ||
| - * @param ended The ending sigil. | ||
| - */ | ||
| - public Sigils( final String began, final String ended ) { | ||
| - super( began, ended ); | ||
| - | ||
| - assert began != null; | ||
| - assert !began.isBlank(); | ||
| - assert ended != null; | ||
| - assert !ended.isBlank(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * @return The opening sigil token. | ||
| - */ | ||
| - public String getBegan() { | ||
| - return getKey(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * @return The closing sigil token, or the empty string if none set. | ||
| - */ | ||
| - public String getEnded() { | ||
| - return getValue(); | ||
| - } | ||
| - | ||
| - @Override | ||
| - public String toString() { | ||
| - return getBegan() + getEnded(); | ||
| - } | ||
| -} | ||
| -/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | ||
| -package com.keenwrite.sigils; | ||
| - | ||
| -/** | ||
| - * Responsible for bracketing definition keys with token delimiters. | ||
| - */ | ||
| -public final class YamlSigilOperator extends SigilOperator { | ||
| - public YamlSigilOperator( final Sigils sigils ) { | ||
| - super( sigils ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Removes start and stop definition key delimiters from the given key. | ||
| - * | ||
| - * @param key The key that may have start and stop tokens. | ||
| - * @return The given key with the delimiters removed. | ||
| - */ | ||
| - public String detoken( final String key ) { | ||
| - final var began = getBegan(); | ||
| - final var ended = getEnded(); | ||
| - final int bLength = began.length(); | ||
| - final int eLength = ended.length(); | ||
| - final var bIndex = key.indexOf( began ); | ||
| - final var eIndex = key.indexOf( ended, bIndex ); | ||
| - final var kLength = key.length(); | ||
| - | ||
| - return key.substring( | ||
| - bIndex == -1 ? 0 : bLength, eIndex == -1 ? kLength : kLength - eLength ); | ||
| - } | ||
| -} | ||