diff --git a/README.md b/README.md
index 36766c3..5072d4a 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,7 @@
-# dub-registry-search
-A search implementation for code.dlang.org
+# D Package Search
+
+An indexer and search API for D programming language packages as registered on https://code.dlang.org, using Apache Lucene.
+
+## Setup
+
+To set up and run the program, all you need is Java version 21 or higher, and then run the project using your favorite IDE. It will boot up a web server that you can use to search for packages at http://localhost:8080/search?query=test, replacing `query=test` with what you want to search for.
diff --git a/pom.xml b/pom.xml
index b991d90..23c2170 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,13 +4,13 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- io.github.andrewlalis
- dub-registry-search
+ com.andrewlalis
+ d-package-search
1.0.0-SNAPSHOT
- 17
- 17
+ 21
+ 21
UTF-8
@@ -19,15 +19,23 @@
org.apache.lucene
lucene-core
- 9.5.0
+ 9.8.0
com.fasterxml.jackson.core
jackson-databind
- 2.14.2
+ 2.15.1
+
+
+
+ org.eclipse.jetty
+ jetty-server
+ 12.0.1
+
+
\ No newline at end of file
diff --git a/src/main/java/com/andrewlalis/d_package_search/DPackageSearch.java b/src/main/java/com/andrewlalis/d_package_search/DPackageSearch.java
new file mode 100644
index 0000000..f67836c
--- /dev/null
+++ b/src/main/java/com/andrewlalis/d_package_search/DPackageSearch.java
@@ -0,0 +1,39 @@
+package com.andrewlalis.d_package_search;
+
+import com.andrewlalis.d_package_search.impl.DubRegistryPackageFetcher;
+import com.andrewlalis.d_package_search.impl.LucenePackageIndexer;
+import com.andrewlalis.d_package_search.impl.LucenePackageSearcher;
+
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.ArrayList;
+
+public class DPackageSearch {
+ public static void main(String[] args) {
+ Path indexPath = Path.of("package-index");
+ startIndexerThread(new IndexGenerator(
+ new DubRegistryPackageFetcher(),
+ () -> new LucenePackageIndexer(indexPath)
+ ));
+ new WebApiRunner(new LucenePackageSearcher(indexPath)).run();
+ }
+
+ /**
+ * Starts a new (virtual) thread that periodically re-generates the package
+ * index.
+ * @param indexGenerator The index generator to use.
+ */
+ public static void startIndexerThread(IndexGenerator indexGenerator) {
+ Thread.ofVirtual().start(() -> {
+ while (true) {
+ indexGenerator.run();
+ try {
+ Thread.sleep(Duration.ofMinutes(1));
+ } catch (InterruptedException e) {
+ System.err.println("Indexing thread interrupted: " + e.getMessage());
+ break;
+ }
+ }
+ });
+ }
+}
diff --git a/src/main/java/com/andrewlalis/d_package_search/IndexGenerator.java b/src/main/java/com/andrewlalis/d_package_search/IndexGenerator.java
new file mode 100644
index 0000000..4ec5ecf
--- /dev/null
+++ b/src/main/java/com/andrewlalis/d_package_search/IndexGenerator.java
@@ -0,0 +1,45 @@
+package com.andrewlalis.d_package_search;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collection;
+
+/**
+ * The index generator is a component that pieces all the parts of building an
+ * index together into one runnable. It fetches packages using a fetcher, then
+ * indexes them using an indexer obtained from the given supplier.
+ * @param fetcher The fetcher to use to get packages.
+ * @param indexerSupplier A supplier for a package indexer.
+ */
+public record IndexGenerator(
+ PackageFetcher fetcher,
+ ThrowableSupplier indexerSupplier
+) implements Runnable {
+ @Override
+ public void run() {
+ System.out.println("Generating index...");
+ Instant start;
+ Duration dur;
+ start = Instant.now();
+ Collection packages;
+ try {
+ packages = fetcher.fetch();
+ } catch (IOException e) {
+ System.err.println("Failed to fetch packages: " + e.getMessage());
+ return;
+ }
+ try (PackageIndexer indexer = indexerSupplier.get()) {
+ dur = Duration.between(start, Instant.now());
+ System.out.println("Fetched " + packages.size() + " in " + dur.toMillis() + " ms.");
+ start = Instant.now();
+ for (var pkg : packages) {
+ indexer.addToIndex(pkg);
+ }
+ dur = Duration.between(start, Instant.now());
+ System.out.println("Indexed all packages in " + dur.toMillis() + " ms.");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/andrewlalis/d_package_search/PackageFetcher.java b/src/main/java/com/andrewlalis/d_package_search/PackageFetcher.java
new file mode 100644
index 0000000..3c85f70
--- /dev/null
+++ b/src/main/java/com/andrewlalis/d_package_search/PackageFetcher.java
@@ -0,0 +1,11 @@
+package com.andrewlalis.d_package_search;
+
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * A component responsible for fetching up-to-date information about packages.
+ */
+public interface PackageFetcher {
+ Collection fetch() throws IOException;
+}
diff --git a/src/main/java/com/andrewlalis/d_package_search/PackageIndexer.java b/src/main/java/com/andrewlalis/d_package_search/PackageIndexer.java
new file mode 100644
index 0000000..96e2b30
--- /dev/null
+++ b/src/main/java/com/andrewlalis/d_package_search/PackageIndexer.java
@@ -0,0 +1,12 @@
+package com.andrewlalis.d_package_search;
+
+/**
+ * A package indexer writes information from a given JSON package object to an
+ * index for searching later.
+ */
+public interface PackageIndexer extends AutoCloseable {
+ void addToIndex(PackageInfo info) throws Exception;
+
+ @Override
+ default void close() throws Exception {}
+}
diff --git a/src/main/java/com/andrewlalis/d_package_search/PackageInfo.java b/src/main/java/com/andrewlalis/d_package_search/PackageInfo.java
new file mode 100644
index 0000000..ec40ff7
--- /dev/null
+++ b/src/main/java/com/andrewlalis/d_package_search/PackageInfo.java
@@ -0,0 +1,33 @@
+package com.andrewlalis.d_package_search;
+
+import java.time.LocalDateTime;
+
+/**
+ * Information about a D package that is ready for indexing.
+ * @param name The name of the package.
+ * @param categories The list of categories the package is in.
+ * @param versions The known list of versions for this package.
+ */
+public record PackageInfo(
+ String name,
+ String[] categories,
+ VersionInfo[] versions
+) {
+ /**
+ * Information about a specific version of a D package.
+ * @param timestamp The timestamp (in UTC) when the version was published.
+ * @param versionTag The version tag string (e.g. "1.2.3").
+ * @param description The version's description, or null.
+ * @param license The version's license name (like "MIT" or "LGPL"), or null.
+ * @param authors The list of authors for this version.
+ * @param readmeText The text content of this version's README file.
+ */
+ public record VersionInfo(
+ LocalDateTime timestamp,
+ String versionTag,
+ String description,
+ String license,
+ String[] authors,
+ String readmeText
+ ) {}
+}
diff --git a/src/main/java/com/andrewlalis/d_package_search/PackageSearchResult.java b/src/main/java/com/andrewlalis/d_package_search/PackageSearchResult.java
new file mode 100644
index 0000000..518308c
--- /dev/null
+++ b/src/main/java/com/andrewlalis/d_package_search/PackageSearchResult.java
@@ -0,0 +1,6 @@
+package com.andrewlalis.d_package_search;
+
+public record PackageSearchResult(
+ String name,
+ String url
+) {}
diff --git a/src/main/java/com/andrewlalis/d_package_search/PackageSearcher.java b/src/main/java/com/andrewlalis/d_package_search/PackageSearcher.java
new file mode 100644
index 0000000..e49e959
--- /dev/null
+++ b/src/main/java/com/andrewlalis/d_package_search/PackageSearcher.java
@@ -0,0 +1,7 @@
+package com.andrewlalis.d_package_search;
+
+import java.util.SequencedCollection;
+
+public interface PackageSearcher {
+ SequencedCollection search(String query);
+}
diff --git a/src/main/java/com/andrewlalis/d_package_search/ThrowableSupplier.java b/src/main/java/com/andrewlalis/d_package_search/ThrowableSupplier.java
new file mode 100644
index 0000000..365bfbc
--- /dev/null
+++ b/src/main/java/com/andrewlalis/d_package_search/ThrowableSupplier.java
@@ -0,0 +1,6 @@
+package com.andrewlalis.d_package_search;
+
+@FunctionalInterface
+public interface ThrowableSupplier {
+ T get() throws Exception;
+}
diff --git a/src/main/java/com/andrewlalis/d_package_search/WebApiRunner.java b/src/main/java/com/andrewlalis/d_package_search/WebApiRunner.java
new file mode 100644
index 0000000..ddc9375
--- /dev/null
+++ b/src/main/java/com/andrewlalis/d_package_search/WebApiRunner.java
@@ -0,0 +1,88 @@
+package com.andrewlalis.d_package_search;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.server.*;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+import java.net.URLDecoder;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.SequencedCollection;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * Component that runs a simple HTTP endpoint, defaulting to localhost:8080/search?query=...
+ * that allows clients to search the index via an HTTP request.
+ */
+public final class WebApiRunner extends Handler.Abstract implements Runnable {
+ private final PackageSearcher packageSearcher;
+ private final ObjectMapper objectMapper;
+ private final Executor threadPoolExecutor;
+
+ public WebApiRunner(PackageSearcher packageSearcher) {
+ this.packageSearcher = packageSearcher;
+ this.objectMapper = new ObjectMapper();
+ this.threadPoolExecutor = Executors.newVirtualThreadPerTaskExecutor();
+ }
+
+ @Override
+ public void run() {
+ QueuedThreadPool threadPool = new QueuedThreadPool();
+ threadPool.setVirtualThreadsExecutor(threadPoolExecutor);
+ threadPool.setName("http-server");
+ Server server = new Server(threadPool);
+ ServerConnector connector = new ServerConnector(server);
+ connector.setPort(8080);
+ server.addConnector(connector);
+ server.setHandler(this);
+ try {
+ server.start();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean handle(Request request, Response response, Callback callback) throws Exception {
+ if (request.getMethod().equalsIgnoreCase("GET")) {
+ HttpURI uri = request.getHttpURI();
+ if (uri.getPath().equalsIgnoreCase("/search")) {
+ String query = uri.getQuery() == null ? null : parseQuery(uri);
+ if (query == null || query.isBlank()) {
+ response.setStatus(HttpStatus.BAD_REQUEST_400);
+ response.write(true, ByteBuffer.wrap("Missing required \"query\" parameter.".getBytes(StandardCharsets.UTF_8)), callback);
+ } else {
+ System.out.println("Searching with query \"" + query + "\".");
+ SequencedCollection results = packageSearcher.search(query);
+ response.setStatus(HttpStatus.OK_200);
+ response.getHeaders().add("Content-Type", "application/json; charset=utf-8");
+ byte[] responseBody = objectMapper.writeValueAsBytes(results);
+ response.write(true, ByteBuffer.wrap(responseBody), callback);
+ }
+ } else {
+ response.setStatus(HttpStatus.NOT_FOUND_404);
+ }
+ } else {
+ response.setStatus(HttpStatus.METHOD_NOT_ALLOWED_405);
+ }
+ callback.succeeded();
+ return true;
+ }
+
+ private static String parseQuery(HttpURI uri) {
+ for (String pair : URLDecoder.decode(uri.getQuery(), StandardCharsets.UTF_8).split("&")) {
+ int idx = pair.indexOf('=');
+ if (idx != -1) {
+ String key = pair.substring(0, idx);
+ if (key.trim().equalsIgnoreCase("query")) {
+ return pair.substring(idx + 1).trim().toUpperCase();
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/andrewlalis/d_package_search/impl/DubRegistryPackageFetcher.java b/src/main/java/com/andrewlalis/d_package_search/impl/DubRegistryPackageFetcher.java
new file mode 100644
index 0000000..97ca795
--- /dev/null
+++ b/src/main/java/com/andrewlalis/d_package_search/impl/DubRegistryPackageFetcher.java
@@ -0,0 +1,107 @@
+package com.andrewlalis.d_package_search.impl;
+
+import com.andrewlalis.d_package_search.PackageFetcher;
+import com.andrewlalis.d_package_search.PackageInfo;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * A package fetcher that pulls directly from the Dub registry's JSON dump.
+ */
+public class DubRegistryPackageFetcher implements PackageFetcher {
+ private final HttpClient httpClient = HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(3))
+ .followRedirects(HttpClient.Redirect.NORMAL)
+ .build();
+ private static final String API_URL = "https://code.dlang.org/api/packages/dump";
+
+ @Override
+ public Collection fetch() throws IOException {
+ HttpRequest req = HttpRequest.newBuilder(URI.create(API_URL))
+ .GET()
+ .timeout(Duration.ofSeconds(60))
+ .header("Accept", "application/json")
+ .header("Accept-Encoding", "gzip")
+ .build();
+ try {
+ HttpResponse response = httpClient.send(req, HttpResponse.BodyHandlers.ofInputStream());
+ if (response.statusCode() != 200) {
+ throw new IOException("Response status code " + response.statusCode());
+ }
+ ObjectMapper mapper = new ObjectMapper();
+ try (var in = new GZIPInputStream(response.body())) {
+ ArrayNode array = mapper.readValue(in, ArrayNode.class);
+ Collection packages = new ArrayList<>();
+ for (JsonNode node : array) {
+ if (node.isObject()) {
+ try {
+ packages.add(parsePackage((ObjectNode) node));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return packages;
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private PackageInfo parsePackage(ObjectNode obj) {
+ return new PackageInfo(
+ obj.get("name").asText(),
+ mapJsonArray(obj.withArray("categories"), JsonNode::asText).toArray(new String[0]),
+ mapJsonArray(obj.withArray("versions"), this::parseVersion).toArray(new PackageInfo.VersionInfo[0])
+ );
+ }
+
+ private PackageInfo.VersionInfo parseVersion(JsonNode node) {
+ String description = null;
+ String license = null;
+ String[] authors = new String[0];
+ if (node.hasNonNull("info")) {
+ JsonNode infoNode = node.get("info");
+ if (infoNode.hasNonNull("description")) {
+ description = infoNode.get("description").asText();
+ }
+ if (infoNode.hasNonNull("license")) {
+ license = infoNode.get("license").asText();
+ }
+ if (infoNode.hasNonNull("authors")) {
+ authors = mapJsonArray(infoNode.withArray("authors"), JsonNode::asText).toArray(authors);
+ }
+ }
+ return new PackageInfo.VersionInfo(
+ OffsetDateTime.parse(node.get("date").asText()).atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime(),
+ node.get("version").asText(),
+ description,
+ license,
+ authors,
+ node.get("readme").asText()
+ );
+ }
+
+ private static List mapJsonArray(ArrayNode array, Function mapper) {
+ List list = new ArrayList<>(array.size());
+ for (JsonNode node : array) {
+ list.add(mapper.apply(node));
+ }
+ return list;
+ }
+}
diff --git a/src/main/java/io/github/andrewlalis/dub_registry_search/LucenePackageIndexer.java b/src/main/java/com/andrewlalis/d_package_search/impl/LucenePackageIndexer.java
similarity index 66%
rename from src/main/java/io/github/andrewlalis/dub_registry_search/LucenePackageIndexer.java
rename to src/main/java/com/andrewlalis/d_package_search/impl/LucenePackageIndexer.java
index 950a3c6..bea2917 100644
--- a/src/main/java/io/github/andrewlalis/dub_registry_search/LucenePackageIndexer.java
+++ b/src/main/java/com/andrewlalis/d_package_search/impl/LucenePackageIndexer.java
@@ -1,6 +1,7 @@
-package io.github.andrewlalis.dub_registry_search;
+package com.andrewlalis.d_package_search.impl;
-import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.andrewlalis.d_package_search.PackageIndexer;
+import com.andrewlalis.d_package_search.PackageInfo;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
@@ -15,7 +16,7 @@ import org.apache.lucene.store.FSDirectory;
import java.io.IOException;
import java.nio.file.Path;
-public class LucenePackageIndexer implements PackageIndexer, AutoCloseable {
+public class LucenePackageIndexer implements PackageIndexer {
private final IndexWriter indexWriter;
private final Directory dir;
private final Analyzer analyzer;
@@ -29,17 +30,14 @@ public class LucenePackageIndexer implements PackageIndexer, AutoCloseable {
this.indexWriter = new IndexWriter(dir, config);
}
-
@Override
- public void addToIndex(ObjectNode packageJson) throws IOException {
- String registryId = packageJson.get("_id").asText();
- String name = packageJson.get("name").asText();
- String dubUrl = "https://code.dlang.org/packages/" + name;
+ public void addToIndex(PackageInfo info) throws IOException {
+ String dubUrl = "https://code.dlang.org/packages/" + info.name();
Document doc = new Document();
- doc.add(new StoredField("registryId", registryId));
- doc.add(new TextField("name", name, Field.Store.YES));
- doc.add(new StoredField("dubUrl", dubUrl));
+ doc.add(new TextField("name", info.name(), Field.Store.YES));
+ doc.add(new StoredField("url", dubUrl));
+ indexWriter.addDocument(doc);
}
@Override
diff --git a/src/main/java/com/andrewlalis/d_package_search/impl/LucenePackageSearcher.java b/src/main/java/com/andrewlalis/d_package_search/impl/LucenePackageSearcher.java
new file mode 100644
index 0000000..9377491
--- /dev/null
+++ b/src/main/java/com/andrewlalis/d_package_search/impl/LucenePackageSearcher.java
@@ -0,0 +1,69 @@
+package com.andrewlalis.d_package_search.impl;
+
+import com.andrewlalis.d_package_search.PackageSearchResult;
+import com.andrewlalis.d_package_search.PackageSearcher;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.*;
+import org.apache.lucene.store.FSDirectory;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.SequencedCollection;
+import java.util.concurrent.Executors;
+
+public class LucenePackageSearcher implements PackageSearcher {
+ private final Path indexPath;
+
+ public LucenePackageSearcher(Path indexPath) {
+ this.indexPath = indexPath;
+ }
+
+ @Override
+ public SequencedCollection search(String query) {
+ if (query == null || query.isBlank() || Files.notExists(indexPath)) return Collections.emptyList();
+ Query luceneQuery = buildQuery(query);
+
+ try (DirectoryReader dirReader = DirectoryReader.open(FSDirectory.open(indexPath))) {
+ IndexSearcher searcher = new IndexSearcher(dirReader, Executors.newVirtualThreadPerTaskExecutor());
+ TopDocs topDocs = searcher.search(luceneQuery, 25, Sort.RELEVANCE, false);
+ List results = new ArrayList<>(25);
+ for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
+ Document doc = searcher.storedFields().document(scoreDoc.doc);
+ results.add(prepareResult(doc));
+ }
+ return results;
+ } catch (IOException e) {
+ System.err.println("An IOException occurred while reading index: " + e.getMessage());
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Builds the Lucene search query for a given textual query string.
+ * @param queryText The query text to use.
+ * @return The query to use.
+ */
+ private Query buildQuery(String queryText) {
+ BooleanQuery.Builder queryBuilder = new BooleanQuery.Builder();
+ String[] searchTerms = queryText.toLowerCase().split("\\s+");
+ for (String searchTerm : searchTerms) {
+ String wildcardTerm = searchTerm + "*";
+ Query basicQuery = new WildcardQuery(new Term("name", wildcardTerm));
+ queryBuilder.add(new BoostQuery(basicQuery, 1f), BooleanClause.Occur.SHOULD);
+ }
+ return queryBuilder.build();
+ }
+
+ private PackageSearchResult prepareResult(Document doc) {
+ return new PackageSearchResult(
+ doc.get("name"),
+ doc.get("url")
+ );
+ }
+}
diff --git a/src/main/java/io/github/andrewlalis/dub_registry_search/DubPackageFetcher.java b/src/main/java/io/github/andrewlalis/dub_registry_search/DubPackageFetcher.java
deleted file mode 100644
index a4fb6c5..0000000
--- a/src/main/java/io/github/andrewlalis/dub_registry_search/DubPackageFetcher.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package io.github.andrewlalis.dub_registry_search;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.time.Duration;
-import java.util.zip.GZIPInputStream;
-
-public class DubPackageFetcher implements PackageFetcher {
- private final HttpClient httpClient = HttpClient.newBuilder()
- .connectTimeout(Duration.ofSeconds(3))
- .followRedirects(HttpClient.Redirect.NORMAL)
- .build();
- private static final String API_URL = "https://code.dlang.org/api/packages/dump";
-
- @Override
- public ArrayNode fetch() throws IOException {
- HttpRequest req = HttpRequest.newBuilder(URI.create(API_URL))
- .GET()
- .timeout(Duration.ofSeconds(60))
- .header("Accept", "application/json")
- .header("Accept-Encoding", "gzip")
- .build();
- try {
- HttpResponse response = httpClient.send(req, HttpResponse.BodyHandlers.ofInputStream());
- if (response.statusCode() != 200) {
- throw new IOException("Response status code " + response.statusCode());
- }
- ObjectMapper mapper = new ObjectMapper();
- try (var in = new GZIPInputStream(response.body())) {
- return mapper.readValue(in, ArrayNode.class);
- }
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/src/main/java/io/github/andrewlalis/dub_registry_search/DubRegistrySearch.java b/src/main/java/io/github/andrewlalis/dub_registry_search/DubRegistrySearch.java
deleted file mode 100644
index 72631d9..0000000
--- a/src/main/java/io/github/andrewlalis/dub_registry_search/DubRegistrySearch.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package io.github.andrewlalis.dub_registry_search;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.time.Duration;
-import java.time.Instant;
-
-public class DubRegistrySearch {
- public static void main(String[] args) throws Exception {
- if (args.length == 1 && args[0].strip().equalsIgnoreCase("index")) {
- buildIndex();
- }
- }
-
- public static void buildIndex() throws Exception {
- System.out.println("Building package index.");
- PackageFetcher fetcher = new DubPackageFetcher();
- System.out.println("Fetching packages...");
- ArrayNode packagesArray = fetcher.fetch();
- int docCount = 0;
- Duration indexDuration;
- try (var indexer = new LucenePackageIndexer(Path.of("package-index"))) {
- Instant start = Instant.now();
- for (JsonNode node : packagesArray) {
- if (node.isObject()) {
- try {
- indexer.addToIndex((ObjectNode) node);
- docCount++;
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- Instant end = Instant.now();
- indexDuration = Duration.between(start, end);
- }
- System.out.println("Done! Added " + docCount + " packages to the index in " + indexDuration.toMillis() + " ms.");
- }
-}
diff --git a/src/main/java/io/github/andrewlalis/dub_registry_search/PackageFetcher.java b/src/main/java/io/github/andrewlalis/dub_registry_search/PackageFetcher.java
deleted file mode 100644
index 3eca6a6..0000000
--- a/src/main/java/io/github/andrewlalis/dub_registry_search/PackageFetcher.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.github.andrewlalis.dub_registry_search;
-
-import com.fasterxml.jackson.databind.node.ArrayNode;
-
-import java.io.IOException;
-
-public interface PackageFetcher {
- ArrayNode fetch() throws IOException;
-}
diff --git a/src/main/java/io/github/andrewlalis/dub_registry_search/PackageIndexer.java b/src/main/java/io/github/andrewlalis/dub_registry_search/PackageIndexer.java
deleted file mode 100644
index 1f5b0cc..0000000
--- a/src/main/java/io/github/andrewlalis/dub_registry_search/PackageIndexer.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.github.andrewlalis.dub_registry_search;
-
-import com.fasterxml.jackson.databind.node.ObjectNode;
-
-import java.io.IOException;
-
-public interface PackageIndexer {
- void addToIndex(ObjectNode packageJson) throws IOException;
-}