Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
dafaeba
Simplyfy code
koppor Nov 6, 2025
5e85c5d
Fix step ignore
koppor Nov 6, 2025
5b1ffaf
Add doi_to_bibtex.java
koppor Nov 6, 2025
3f66895
Add doi-to-bibtex to JabKit
koppor Nov 6, 2025
242436d
Add CHANGELOG.md entry
koppor Nov 6, 2025
5ed9435
Add some debug code (again)
koppor Nov 6, 2025
f765241
Fix formatting
koppor Nov 6, 2025
c803beb
Fix logger
koppor Nov 6, 2025
df625ab
Also deal with jabkit
koppor Nov 6, 2025
d3dcc6b
Remove debug
koppor Nov 6, 2025
3455a03
Fix casing
koppor Nov 6, 2025
7ea1a65
Fix condition
koppor Nov 6, 2025
f19675b
Merge remote-tracking branch 'origin/main' into add-doi-to-bibtex-exa…
koppor Nov 9, 2025
d2aca73
Continue on wrong DOI
koppor Nov 9, 2025
6ebc93c
Merge remote-tracking branch 'origin/main' into add-doi-to-bibtex-exa…
koppor Nov 9, 2025
b68c6e0
Apply suggestions from code review
koppor Nov 9, 2025
05c4a8b
Fix checkstyle
koppor Nov 9, 2025
7ebb00b
Merge branch 'add-doi-to-bibtex-example' of github.com:JabRef/jabref …
koppor Nov 9, 2025
1f88fb3
Fix space
koppor Nov 9, 2025
19e0ded
Disable JUL output at JabKit
koppor Nov 9, 2025
8b14408
--porcelain does not output any logs to the console any more
koppor Nov 9, 2025
fd0b7ef
Fix logger config
koppor Nov 9, 2025
067af4f
Have --porcelain working
koppor Nov 9, 2025
43b4b87
Improve strings
koppor Nov 9, 2025
b439fdf
Update CHANGELOG.md
subhramit Nov 9, 2025
bea7fc5
Update jabkit/src/main/java/org/jabref/cli/DoiToBibtex.java
subhramit Nov 9, 2025
8faed65
Merge branch 'main' into add-doi-to-bibtex-example
koppor Nov 9, 2025
d4b287d
Merge branch 'main' into add-doi-to-bibtex-example
subhramit Nov 9, 2025
fd63a0d
Merge branch 'main' into add-doi-to-bibtex-example
koppor Nov 9, 2025
06b5c48
Workaround for tests
koppor Nov 9, 2025
1644f8b
Compilefix
koppor Nov 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/workflows/tests-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ jobs:
run: |
echo "cache_key=jbang-$(date +%F)" >> $GITHUB_OUTPUT
- name: Use cache
if: steps.changed-jablib-files.outputs.any_changed != 'true'
uses: actions/cache@v4
with:
path: ~/.jbang
Expand Down Expand Up @@ -522,6 +523,7 @@ jobs:
with:
files: |
.jbang/*.java
jabkit/src/main/java/**/*.java
jablib/src/main/java/**/*.java
jablib-examples/**/*.java
files_ignore: |
Expand Down Expand Up @@ -553,7 +555,16 @@ jobs:
# We modify the JBang scripts directly to avoid issues with relative paths
for f in ${{ steps.changed-jablib-files.outputs.all_changed_files }}; do
case "$f" in
jablib-examples/*) continue ;; # skip scripts
jablib-examples/*)
# skip scripts
continue
;;
jabkit/*)
# only JabKit needs its modified sources
if [ "${{ matrix.script }}" != ".jbang/JabKitLauncher.java" ]; then
continue
fi
;;
esac
echo "//SOURCES ../$f" >> "${{ matrix.script }}"
done
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv

- We added "IEEE" as another option for parsing plain text citations. [#14233](github.com/JabRef/jabref/pull/14233)
- We added automatic date-based groups that create year/month/day subgroups from an entry’s date fields. [#10822](https://github.com/JabRef/jabref/issues/10822)
- We added `doi-to-bibtex` to `JabKit`. [#14244](https://github.com/JabRef/jabref/pull/14244)

### Changed

- `JabKit`: `--porcelain` does not output any logs to the console anymore. [#14244](https://github.com/JabRef/jabref/pull/14244)
- <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>L</kbd> now opens the terminal in the active library directory. [#14130](https://github.com/JabRef/jabref/issues/14130)

### Fixed
Expand Down
3 changes: 3 additions & 0 deletions jabkit/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ application {

// Also passed to launcher by java-module-packaging plugin
applicationDefaultJvmArgs = listOf(
// JEP 158: Disable all java util logging
"-Xlog:disable",

// Enable JEP 450: Compact Object Headers
"-XX:+UnlockExperimentalVMOptions", "-XX:+UseCompactObjectHeaders",

Expand Down
31 changes: 18 additions & 13 deletions jabkit/src/main/java/org/jabref/JabKit.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -58,6 +57,8 @@ public class JabKit {

private static final String JABKIT_BRAND = "JabKit - command line toolkit for JabRef";

/// Note: To test with gradle, use jabkit -> Tasks -> application -> run
/// Use `--args="..."` as parameters to "Run"
public static void main(String[] args) {
initLogging(args);

Expand Down Expand Up @@ -143,11 +144,10 @@ public static void initLogging(String[] args) {

// We must configure logging as soon as possible, which is why we cannot wait for the usual
// argument parsing workflow to parse logging options e.g. --debug or --porcelain
boolean isPorcelain = Arrays.stream(args).anyMatch("--porcelain"::equalsIgnoreCase);
Level logLevel;
if (Arrays.stream(args).anyMatch("--debug"::equalsIgnoreCase)) {
logLevel = Level.DEBUG;
} else if (Arrays.stream(args).anyMatch("--porcelain"::equalsIgnoreCase)) {
logLevel = Level.ERROR;
} else {
logLevel = Level.INFO;
}
Expand All @@ -163,18 +163,23 @@ public static void initLogging(String[] args) {
return;
}

String fileWriterName;
if (isPorcelain) {
fileWriterName = "writer";
} else {
fileWriterName = "writerFile";
}

// The "Shared File Writer" is explained at
// https://tinylog.org/v2/configuration/#shared-file-writer
Map<String, String> configuration = Map.of(
"level", logLevel.name().toLowerCase(),
"writerFile", "rolling file",
"writerFile.logLevel", logLevel == Level.DEBUG ? "debug" : "info",
// We need to manually join the path, because ".resolve" does not work on Windows, because ":" is not allowed in file names on Windows
"writerFile.file", directory + File.separator + "log_{date:yyyy-MM-dd_HH-mm-ss}.txt",
"writerFile.charset", "UTF-8",
"writerFile.policies", "startup",
"writerFile.backups", "30");
configuration.forEach(Configuration::set);
Configuration.set("level", logLevel.name().toLowerCase());
Configuration.set(fileWriterName, "rolling file");
Configuration.set("%s.logLevel".formatted(fileWriterName), logLevel == Level.DEBUG ? "debug" : "info");
// We need to manually join the path, because ".resolve" does not work on Windows, because ":" is not allowed in file names on Windows
Configuration.set("%s.file".formatted(fileWriterName), directory + File.separator + "log_{date:yyyy-MM-dd_HH-mm-ss}.txt");
Configuration.set("%s.charset".formatted(fileWriterName), "UTF-8");
Configuration.set("%s.policies".formatted(fileWriterName), "startup");
Configuration.set("%s.backups".formatted(fileWriterName), "30");

LOGGER = LoggerFactory.getLogger(JabKit.class);
}
Expand Down
1 change: 1 addition & 0 deletions jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
CheckConsistency.class,
CheckIntegrity.class,
Convert.class,
DoiToBibtex.class,
Fetch.class,
GenerateBibFromAux.class,
GenerateCitationKeys.class,
Expand Down
87 changes: 87 additions & 0 deletions jabkit/src/main/java/org/jabref/cli/DoiToBibtex.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.jabref.cli;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;

import org.jabref.logic.exporter.BibDatabaseWriter;
import org.jabref.logic.importer.FetcherException;
import org.jabref.logic.importer.fetcher.CrossRef;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.database.BibDatabase;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.identifier.DOI;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;

@Command(name = "doi-to-bibtex", description = "Converts a DOI to BibTeX")
public class DoiToBibtex implements Callable<Integer> {

private static final Logger LOGGER = LoggerFactory.getLogger(DoiToBibtex.class);

@CommandLine.ParentCommand
private ArgumentProcessor argumentProcessor;

@CommandLine.Mixin
private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions();

@Parameters(paramLabel = "DOI", description = "one or more DOIs to fetch", arity = "1..*")
private String[] dois;

@Override
public Integer call() {
CrossRef fetcher = new CrossRef();
List<BibEntry> entries = new ArrayList<>(dois.length);

for (String doiString : dois) {
Optional<DOI> doiParsed = DOI.parse(doiString);
if (doiParsed.isEmpty()) {
LOGGER.warn("Skipped DOI {}, because it is not a valid DOI string", doiString);
System.out.println(Localization.lang("DOI %0 is invalid", doiString));
System.err.println();
continue;
}
Optional<BibEntry> entry;
try {
entry = fetcher.performSearchById(doiParsed.get().asString());
} catch (FetcherException e) {
LOGGER.error("Could not fetch BibTeX based on DOI", e);
System.err.print(Localization.lang("No data was found for the identifier"));
System.err.println(" - " + doiString);
System.err.println(e.getLocalizedMessage());
System.err.println();
continue;
}

if (entry.isEmpty()) {
LOGGER.error("Could not fetch BibTeX based on DOI - entry is empty");
System.err.print(Localization.lang("No data was found for the identifier"));
System.err.println(" - " + doiString);
System.err.println();
continue;
}

entries.add(entry.get());
}

try (OutputStreamWriter writer = new OutputStreamWriter(System.out, StandardCharsets.UTF_8)) {
BibDatabaseContext context = new BibDatabaseContext(new BibDatabase(entries));
BibDatabaseWriter bibWriter = new BibDatabaseWriter(writer, context, argumentProcessor.cliPreferences);
bibWriter.writeDatabase(context);
} catch (IOException e) {
LOGGER.error("Could not write BibTeX", e);
System.err.println(Localization.lang("Unable to write to %0.", "stdout"));
return 1;
}
return 0;
}
}
34 changes: 34 additions & 0 deletions jablib-examples/doi_to_bibtex.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
///usr/bin/env jbang "$0" "$@" ; exit $?

import org.jabref.logic.exporter.BibWriter;
import org.jabref.logic.exporter.BibDatabaseWriter;
import org.jabref.logic.importer.fetcher.CrossRef;
import org.jabref.logic.preferences.JabRefCliPreferences;
import org.jabref.model.database.BibDatabase;
import org.jabref.model.database.BibDatabaseContext;

import org.tinylog.Logger;

//DESCRIPTION Converts a DOI to BibTeX

//JAVA 25+
//RUNTIME_OPTIONS --enable-native-access=ALL-UNNAMED
//FILES tinylog.properties=tinylog.properties

//DEPS org.jabref:jablib:6.0-SNAPSHOT
//REPOS mavencentral,mavencentralsnapshots=https://central.sonatype.com/repository/maven-snapshots/,s01oss=https://s01.oss.sonatype.org/content/repositories/snapshots/,oss=https://oss.sonatype.org/content/repositories,jitpack=https://jitpack.io,oss2=https://oss.sonatype.org/content/groups/public,ossrh=https://oss.sonatype.org/content/repositories/snapshots,raw=https://raw.githubusercontent.com/JabRef/jabref/refs/heads/main/jablib/lib/

void main() throws Exception {
var preferences = JabRefCliPreferences.getInstance();

// All `IdParserFetcher<DOI>` can do. In JabRef, there is currently only one implemented

var fetcher = new CrossRef();
var entry = fetcher.performSearchById("10.47397/tb/44-3/tb138kopp-jabref").get(); // will throw an exception if not found

try (var writer = new OutputStreamWriter(System.out, StandardCharsets.UTF_8)) {
var context = new BibDatabaseContext(new BibDatabase(List.of(entry)));
var bibWriter = new BibDatabaseWriter(writer, context, preferences);
bibWriter.writeDatabase(context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import org.jabref.logic.cleanup.FieldFormatterCleanups;
import org.jabref.logic.cleanup.NormalizeWhitespacesCleanup;
import org.jabref.logic.formatter.bibtexfields.TrimWhitespaceFormatter;
import org.jabref.logic.preferences.JabRefCliPreferences;
import org.jabref.logic.preferences.CliPreferences;
import org.jabref.logic.util.strings.StringUtil;
import org.jabref.model.FieldChange;
import org.jabref.model.database.BibDatabase;
Expand Down Expand Up @@ -95,7 +95,7 @@ public BibDatabaseWriter(@NonNull BibWriter bibWriter,
/// @param preferences - used to read all the preferences
public BibDatabaseWriter(@NonNull Writer writer,
@NonNull BibDatabaseContext bibDatabaseContext,
@NonNull JabRefCliPreferences preferences) {
@NonNull CliPreferences preferences) {
this(new BibWriter(writer, bibDatabaseContext.getDatabase().getNewLineSeparator()),
preferences.getSelfContainedExportConfiguration(),
preferences.getFieldPreferences(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import org.jabref.logic.importer.FetcherException;
import org.jabref.model.entry.BibEntry;
Expand All @@ -19,7 +18,7 @@ default List<BibEntry> parseMultiplePlainCitations(String text) throws FetcherEx
return CitationSplitter.splitCitations(text)
.map(Unchecked.function(this::parsePlainCitation))
.flatMap(Optional::stream)
.collect(Collectors.toList());
.toList();
} catch (UncheckedException e) {
throw (FetcherException) e.getCause();
}
Expand Down
2 changes: 1 addition & 1 deletion jablib/src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,7 @@ No\ problems\ found.=No problems found.
Print\ entry\ preview=Print entry preview

Invalid\ DOI\:\ '%0'.=Invalid DOI: '%0'.
DOI\ %0\ is\ invalid=DOI %0 is invalid
Same\ DOI\ used\ in\ multiple\ entries=Same DOI used in multiple entries
should\ start\ with\ a\ name=should start with a name
should\ end\ with\ a\ name=should end with a name
Expand Down Expand Up @@ -1974,7 +1975,6 @@ Update\ with\ bibliographic\ information\ from\ the\ web=Update with bibliograph

Could\ not\ find\ any\ bibliographic\ information.=Could not find any bibliographic information.
Citation\ key\ deviates\ from\ generated\ key=Citation key deviates from generated key
DOI\ %0\ is\ invalid=DOI %0 is invalid

Select\ all\ customized\ types\ to\ be\ stored\ in\ local\ preferences\:=Select all customized types to be stored in local preferences\:
Different\ customization,\ current\ settings\ will\ be\ overwritten=Different customization, current settings will be overwritten
Expand Down