Skip to content

Commit 72196e7

Browse files
authored
Merge pull request #269 from metafacture/212-addLookupOptionToPrintUnknownValues
Add `lookup()` option to print unknown values.
2 parents 5d756eb + dec5b36 commit 72196e7

File tree

5 files changed

+243
-40
lines changed

5 files changed

+243
-40
lines changed

README.md

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -356,14 +356,16 @@ Parameters:
356356

357357
Options:
358358

359+
- `append`: Whether to open files in append mode if they exist. (Default: `false`)
359360
- `compression` (file output only): Compression mode. (Default: `auto`)
360361
- `destination`: Destination to write the record to; may include [format directives](https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax) for counter and record ID (in that order). (Default: `stdout`)
361362
- `encoding` (file output only): Encoding used by the underlying writer. (Default: `UTF-8`)
362-
- `footer`: Footer which is output after the record. (Default: `\n`)
363-
- `header`: Header which is output before the record. (Default: Empty string)
363+
- `footer`: Footer which is written at the end of the output. (Default: `\n`)
364+
- `header`: Header which is written at the beginning of the output. (Default: Empty string)
364365
- `id`: Field name which contains the record ID; if found, will be available for inclusion in `prefix` and `destination`. (Default: `_id`)
365366
- `internal`: Whether to print the record's internal representation instead of JSON. (Default: `false`)
366367
- `pretty`: Whether to use pretty printing. (Default: `false`)
368+
- `separator`: Separator which is written after the record. (Default: `\n`)
367369

368370
```perl
369371
print_record(["<prefix>"][, <options>...])
@@ -553,10 +555,55 @@ join_field("<sourceField>", "<separator>")
553555

554556
Looks up matching values in a map and replaces the field value with this match. External files as well as internal maps can be used.
555557

558+
Parameters:
559+
560+
- `path` (required): Field path to look up.
561+
- `map` (optional): Name or path of the map in which to look up values.
562+
563+
Options:
564+
565+
- `__default`: Default value to use for unknown values. (Default: Old value)
566+
- `delete`: Whether to delete unknown values. (Default: `false`)
567+
- `print_unknown`: Whether to print unknown values. (Default: `false`)
568+
569+
Additional options when printing unknown values:
570+
571+
- `append`: Whether to open files in append mode if they exist. (Default: `true`)
572+
- `compression` (file output only): Compression mode. (Default: `auto`)
573+
- `destination`: Destination to write unknown values to; may include [format directives](https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax) for counter and record ID (in that order). (Default: `stdout`)
574+
- `encoding` (file output only): Encoding used by the underlying writer. (Default: `UTF-8`)
575+
- `footer`: Footer which is written at the end of the output. (Default: `\n`)
576+
- `header`: Header which is written at the beginning of the output. (Default: Empty string)
577+
- `id`: Field name which contains the record ID; if found, will be available for inclusion in `destination`. (Default: `_id`)
578+
- `prefix`: Prefix to print before the unknown value; may include [format directives](https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax) for counter and record ID (in that order). (Default: Empty string)
579+
- `separator`: Separator which is written after the unknown value. (Default: `\n`)
580+
556581
```perl
557-
lookup("<sourceField>", "<mapFile>", sep_char: ”,”)
558-
lookup("<sourceField>", "<mapName>")
559-
lookup("<sourceField>", "<mapName>", default: "NA")
582+
lookup("<sourceField>"[, <mapName>][, <options>...])
583+
```
584+
585+
E.g.:
586+
587+
```perl
588+
# local (unnamed) map
589+
lookup("path.to.field", key_1: "value_1", ...)
590+
591+
# internal (named) map
592+
put_map("internal-map", key_1: "value_1", ...)
593+
lookup("path.to.field", "internal-map")
594+
595+
# external file map (implicit)
596+
lookup("path.to.field", "path/to/file", sep_char: ";")
597+
598+
# external file map (explicit)
599+
put_filemap("path/to/file", "file-map", sep_char: ";")
600+
lookup("path.to.field", "file-map")
601+
602+
# with default value
603+
lookup("path.to.field", "map-name", __default: "NA")
604+
605+
# with printing unknown values to a file
606+
lookup("path.to.field", "map-name", print_unknown: "true", destination: "unknown.txt")
560607
```
561608

562609
##### `prepend`

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ subprojects {
4141
'jquery': '3.3.1-1',
4242
'junit_jupiter': '5.8.2',
4343
'junit_platform': '1.4.2',
44-
'metafacture': 'metafacture-core-5.4.1-rc1',
44+
'metafacture': 'metafacture-core-5.4.1-rc3',
4545
'mockito': '2.27.0',
4646
'requirejs': '2.3.6',
4747
'slf4j': '1.7.21',

metafix/src/main/java/org/metafacture/metafix/FixMethod.java

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package org.metafacture.metafix;
1818

19-
import org.metafacture.framework.StandardEventNames;
20-
import org.metafacture.io.ObjectWriter;
2119
import org.metafacture.metafix.api.FixFunction;
2220
import org.metafacture.metamorph.api.Maps;
2321
import org.metafacture.metamorph.functions.ISBN;
@@ -34,6 +32,7 @@
3432
import java.util.Map;
3533
import java.util.Random;
3634
import java.util.concurrent.atomic.LongAdder;
35+
import java.util.function.Consumer;
3736
import java.util.function.Function;
3837
import java.util.function.Predicate;
3938
import java.util.function.UnaryOperator;
@@ -241,42 +240,31 @@ private boolean literalString(final String s) {
241240

242241
@Override
243242
public void apply(final Metafix metafix, final Record record, final List<String> params, final Map<String, String> options) {
244-
final String destination = options.getOrDefault("destination", ObjectWriter.STDOUT);
245-
final Value idValue = record.get(options.getOrDefault("id", StandardEventNames.ID));
246-
247243
final boolean internal = getBoolean(options, "internal");
248244
final boolean pretty = getBoolean(options, "pretty");
249245

250-
final LongAdder counter = scopedCounter.computeIfAbsent(metafix, k -> new LongAdder());
251-
counter.increment();
252-
253-
final String id = Value.isNull(idValue) ? "" : idValue.toString();
254-
final String prefix = params.isEmpty() ? "" : String.format(params.get(0), counter.sum(), id);
255-
final ObjectWriter<String> writer = new ObjectWriter<>(String.format(destination, counter.sum(), id));
256-
257-
withOption(options, "compression", writer::setCompression);
258-
withOption(options, "encoding", writer::setEncoding);
259-
withOption(options, "footer", writer::setFooter);
260-
withOption(options, "header", writer::setHeader);
246+
if (!params.isEmpty()) {
247+
options.put("prefix", params.get(0));
248+
}
261249

262-
if (internal) {
263-
if (pretty) {
264-
record.forEach((f, v) -> writer.process(prefix + f + "=" + v));
250+
withWriter(metafix, record, options, scopedCounter, c -> {
251+
if (internal) {
252+
if (pretty) {
253+
record.forEach((f, v) -> c.accept(f + "=" + v));
254+
}
255+
else {
256+
c.accept(record.toString());
257+
}
265258
}
266259
else {
267-
writer.process(prefix + record);
268-
}
269-
}
270-
else {
271-
try {
272-
writer.process(prefix + record.toJson(pretty));
273-
}
274-
catch (final IOException e) {
275-
// Log a warning? Print string representation instead?
260+
try {
261+
c.accept(record.toJson(pretty));
262+
}
263+
catch (final IOException e) {
264+
// Log a warning? Print string representation instead?
265+
}
276266
}
277-
}
278-
279-
writer.closeStream();
267+
});
280268
}
281269
},
282270
random {
@@ -478,6 +466,8 @@ public void apply(final Metafix metafix, final Record record, final List<String>
478466
}
479467
},
480468
lookup {
469+
private final Map<Metafix, LongAdder> scopedCounter = new HashMap<>();
470+
481471
@Override
482472
public void apply(final Metafix metafix, final Record record, final List<String> params, final Map<String, String> options) {
483473
final Map<String, String> map;
@@ -501,10 +491,30 @@ public void apply(final Metafix metafix, final Record record, final List<String>
501491
}
502492

503493
final String defaultValue = map.get(Maps.DEFAULT_MAP_KEY); // TODO: Catmandu uses 'default'
504-
record.transform(params.get(0), oldValue -> {
505-
final String newValue = map.getOrDefault(oldValue, defaultValue);
506-
return newValue != null ? newValue : getBoolean(options, "delete") ? null : oldValue;
494+
final boolean delete = getBoolean(options, "delete");
495+
final boolean printUnknown = getBoolean(options, "print_unknown");
496+
497+
final Consumer<Consumer<String>> consumer = c -> record.transform(params.get(0), oldValue -> {
498+
final String newValue = map.get(oldValue);
499+
if (newValue != null) {
500+
return newValue;
501+
}
502+
else {
503+
if (c != null) {
504+
c.accept(oldValue);
505+
}
506+
507+
return defaultValue != null ? defaultValue : delete ? null : oldValue;
508+
}
507509
});
510+
511+
if (printUnknown) {
512+
options.putIfAbsent("append", "true");
513+
withWriter(metafix, record, options, scopedCounter, consumer);
514+
}
515+
else {
516+
consumer.accept(null);
517+
}
508518
}
509519
},
510520
prepend {

metafix/src/main/java/org/metafacture/metafix/api/FixFunction.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.metafacture.metafix.api;
1818

19+
import org.metafacture.framework.StandardEventNames;
20+
import org.metafacture.io.ObjectWriter;
1921
import org.metafacture.metafix.Metafix;
2022
import org.metafacture.metafix.Record;
2123
import org.metafacture.metafix.Value;
@@ -24,8 +26,10 @@
2426
import java.util.List;
2527
import java.util.Map;
2628
import java.util.Set;
29+
import java.util.concurrent.atomic.LongAdder;
2730
import java.util.function.BiFunction;
2831
import java.util.function.Consumer;
32+
import java.util.function.UnaryOperator;
2933
import java.util.stream.Stream;
3034

3135
@FunctionalInterface
@@ -43,6 +47,38 @@ default <T> void withOption(final Map<String, String> options, final String key,
4347
}
4448
}
4549

50+
default void withWriter(final Map<String, String> options, final UnaryOperator<String> operator, final Consumer<ObjectWriter<String>> consumer) {
51+
final String destination = options.getOrDefault("destination", ObjectWriter.STDOUT);
52+
final ObjectWriter<String> writer = new ObjectWriter<>(operator != null ? operator.apply(destination) : destination);
53+
54+
withOption(options, "append", writer::setAppendIfFileExists, this::getBoolean);
55+
withOption(options, "compression", writer::setCompression);
56+
withOption(options, "encoding", writer::setEncoding);
57+
withOption(options, "footer", writer::setFooter);
58+
withOption(options, "header", writer::setHeader);
59+
withOption(options, "separator", writer::setSeparator);
60+
61+
try {
62+
consumer.accept(writer);
63+
}
64+
finally {
65+
writer.closeStream();
66+
}
67+
}
68+
69+
default void withWriter(final Metafix metafix, final Record record, final Map<String, String> options, final Map<Metafix, LongAdder> scopedCounter, final Consumer<Consumer<String>> consumer) {
70+
final Value idValue = record.get(options.getOrDefault("id", StandardEventNames.ID));
71+
72+
final LongAdder counter = scopedCounter.computeIfAbsent(metafix, k -> new LongAdder());
73+
counter.increment();
74+
75+
final UnaryOperator<String> formatter = s -> String.format(s,
76+
counter.sum(), Value.isNull(idValue) ? "" : idValue.toString());
77+
78+
final String prefix = formatter.apply(options.getOrDefault("prefix", ""));
79+
withWriter(options, formatter, w -> consumer.accept(s -> w.process(prefix + s)));
80+
}
81+
4682
default boolean getBoolean(final Map<String, String> options, final String key) {
4783
return Boolean.parseBoolean(options.get(key));
4884
}

metafix/src/test/java/org/metafacture/metafix/MetafixLookupTest.java

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.mockito.Mock;
2525
import org.mockito.junit.jupiter.MockitoExtension;
2626

27+
import java.io.IOException;
2728
import java.util.Arrays;
2829

2930
/**
@@ -846,6 +847,115 @@ public void shouldFailLookupInUnknownExternalMap() {
846847
);
847848
}
848849

850+
private void shouldPrintUnknown(final String args, final String defaultValue, final String expected) {
851+
MetafixTestHelpers.assertStdout(expected, () ->
852+
MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList(
853+
LOOKUP + " Aloha: Alohaeha, 'Moin': 'Moin zäme'" + args + ", print_unknown: 'true')"
854+
),
855+
i -> {
856+
i.startRecord("rec1");
857+
i.literal("name", "moe");
858+
i.literal("title", "Aloha");
859+
i.literal("title", "Moin");
860+
i.literal("title", "Hey");
861+
i.literal("title", "you");
862+
i.literal("title", "there");
863+
i.endRecord();
864+
865+
i.startRecord("rec2");
866+
i.literal("name", "joe");
867+
i.literal("title", "Aloha");
868+
i.literal("title", "you");
869+
i.literal("title", "too");
870+
i.endRecord();
871+
},
872+
(o, f) -> {
873+
final boolean delete = "__delete".equals(defaultValue);
874+
875+
o.get().startRecord("rec1");
876+
o.get().literal("name", "moe");
877+
o.get().literal("title", "Alohaeha");
878+
o.get().literal("title", "Moin zäme");
879+
880+
if (defaultValue == null) {
881+
o.get().literal("title", "Hey");
882+
o.get().literal("title", "you");
883+
o.get().literal("title", "there");
884+
}
885+
else if (!delete) {
886+
f.apply(3).literal("title", defaultValue);
887+
}
888+
889+
o.get().endRecord();
890+
891+
o.get().startRecord("rec2");
892+
o.get().literal("name", "joe");
893+
o.get().literal("title", "Alohaeha");
894+
895+
if (defaultValue == null) {
896+
o.get().literal("title", "you");
897+
o.get().literal("title", "too");
898+
}
899+
else if (!delete) {
900+
f.apply(2).literal("title", defaultValue);
901+
}
902+
903+
o.get().endRecord();
904+
}
905+
)
906+
);
907+
}
908+
909+
@Test
910+
public void shouldPrintUnknown() {
911+
shouldPrintUnknown("", null, "Hey\nyou\nthere\nyou\ntoo\n");
912+
}
913+
914+
@Test
915+
public void shouldPrintUnknownWithDefault() {
916+
shouldPrintUnknown(", __default: Tach", "Tach", "Hey\nyou\nthere\nyou\ntoo\n");
917+
}
918+
919+
@Test
920+
public void shouldPrintUnknownWithDelete() {
921+
shouldPrintUnknown(", delete: 'true'", "__delete", "Hey\nyou\nthere\nyou\ntoo\n");
922+
}
923+
924+
@Test
925+
public void shouldPrintUnknownWithPrefix() {
926+
shouldPrintUnknown(", prefix: '<%d:%s>'", null, "<1:rec1>Hey\n<1:rec1>you\n<1:rec1>there\n<2:rec2>you\n<2:rec2>too\n");
927+
}
928+
929+
@Test
930+
public void shouldPrintUnknownWithPrefixAndIdField() {
931+
shouldPrintUnknown(", prefix: '<%d:%s>', id: 'name'", null, "<1:moe>Hey\n<1:moe>you\n<1:moe>there\n<2:joe>you\n<2:joe>too\n");
932+
}
933+
934+
@Test
935+
public void shouldPrintUnknownWithHeader() {
936+
shouldPrintUnknown(", header: '<%d:%s>'", null, "<%d:%s>Hey\nyou\nthere\n<%d:%s>you\ntoo\n");
937+
}
938+
939+
@Test
940+
public void shouldPrintUnknownWithFooter() {
941+
shouldPrintUnknown(", footer: '<%d:%s>'", null, "Hey\nyou\nthere<%d:%s>you\ntoo<%d:%s>");
942+
}
943+
944+
@Test
945+
public void shouldPrintUnknownWithSeparator() {
946+
shouldPrintUnknown(", separator: '<%d:%s>'", null, "Hey<%d:%s>you<%d:%s>there\nyou<%d:%s>too\n");
947+
}
948+
949+
@Test
950+
public void shouldPrintUnknownToFile() throws IOException {
951+
MetafixTestHelpers.assertTempFile("Hey\nyou\nthere\nyou\ntoo\n", p -> shouldPrintUnknown(", destination: '" + p + "'", null, ""));
952+
}
953+
954+
@Test
955+
public void shouldPrintUnknownToFileWithoutAppend() throws IOException {
956+
MetafixTestHelpers.assertTempFile("you\ntoo\n", p -> shouldPrintUnknown(", destination: '" + p + "', append: 'false'", null, ""));
957+
}
958+
849959
private void assertMap(final String... fixDef) {
850960
MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList(fixDef),
851961
i -> {

0 commit comments

Comments
 (0)