Skip to content

Commit

Permalink
refactor ls to separate command
Browse files Browse the repository at this point in the history
  • Loading branch information
heuermh committed Jun 12, 2024
1 parent 9babbb2 commit 69cd801
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 98 deletions.
98 changes: 5 additions & 93 deletions src/main/java/com/github/heuermh/cooper/Cooper.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import picocli.CommandLine.Command;
import picocli.CommandLine.HelpCommand;
import picocli.CommandLine.ITypeConverter;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.ScopeType;

import software.amazon.awssdk.regions.Region;
Expand All @@ -53,6 +54,7 @@
name = "coop",
scope = ScopeType.INHERIT,
subcommands = {
Ls.class,
HelpCommand.class,
GenerateCompletion.class
},
Expand All @@ -62,94 +64,10 @@
resourceBundle = "com.github.heuermh.cooper.Messages",
versionProvider = com.github.heuermh.cooper.About.class
)
public final class Cooper implements Callable<Integer> {
public final class Cooper {

@picocli.CommandLine.Option(
names = { "--region" },
type = Region.class,
converter = RegionConverter.class,
defaultValue = "us-west-2"
)
private Region region;

@picocli.CommandLine.Option(names = { "--human-readable" })
private boolean humanReadable;

@picocli.CommandLine.Option(names = { "--show-header" })
private boolean showHeader;

@picocli.CommandLine.Option(names = { "--reverse-columns" })
private boolean reverseColumns;

@picocli.CommandLine.Option(names = { "--verbose" })
private boolean verbose;

@picocli.CommandLine.Parameters(index = "0..*", arity = "1..*", descriptionKey = "uris")
private List<String> uris;

/** Logger. */
static Logger logger;

/** Human readable formatter. */
static final HumanReadableFormatter FORMATTER = new HumanReadableFormatter();

/** s3 bucket and prefix regex pattern. */
static final Pattern S3_URI = Pattern.compile("^s3:\\/\\/([a-zA-Z-]+)\\/*(.*)$");


@Override
public Integer call() throws Exception {

S3Client s3 = S3Client.builder()
.region(region)
.build();

if (showHeader) {
System.out.println(reverseColumns ? "size\turi" : "uri\tsize");
}

for (String uri : uris) {
Matcher m = S3_URI.matcher(uri);
if (m.matches()) {
String bucket = m.group(1);
String prefix = m.group(2);

logger.info("valid uri={} bucket={} prefix={}", uri, bucket, prefix);

ListObjectsV2Request.Builder requestBuilder = ListObjectsV2Request.builder()
.bucket(bucket);

if (prefix != null && prefix.trim().length() > 0) {
requestBuilder = requestBuilder.prefix(prefix);
}

ListObjectsV2Request request = requestBuilder.build();

logger.info("ListObjectsV2 request={}", request.toString());

ListObjectsV2Iterable responses = s3.listObjectsV2Paginator(request);
for (ListObjectsV2Response response : responses) {

logger.info("ListObjectsV2 response={}", response.toString());

for (S3Object content : response.contents()) {

String s3Path = "s3://" + bucket + "/" + content.key();

if (s3Path.startsWith(uri)) {

String size = humanReadable ? FORMATTER.format(content.size()) : String.valueOf(content.size());
System.out.println(reverseColumns ? size + "\t" + s3Path : s3Path + "\t" + size);
}
}
}
}
else {
logger.warn("uri {} not a valid s3 URI", uri);
}
}
return 0;
}
@Parameters(hidden = true)
private List<String> ignored;


/**
Expand All @@ -159,12 +77,6 @@ public Integer call() throws Exception {
*/
public static void main(final String[] args) {

// cheat to set system property before initializing logger
if (Arrays.asList(args).contains("--verbose")) {
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info");
}
logger = LoggerFactory.getLogger(Cooper.class);

// install a signal handler to exit on SIGPIPE
sun.misc.Signal.handle(new sun.misc.Signal("PIPE"), new sun.misc.SignalHandler() {
@Override
Expand Down
158 changes: 158 additions & 0 deletions src/main/java/com/github/heuermh/cooper/Ls.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* The authors of this file license it to you under the
* Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.github.heuermh.cooper;

import java.util.Arrays;
import java.util.List;

import java.util.concurrent.Callable;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import picocli.AutoComplete.GenerateCompletion;

import picocli.CommandLine.Command;
import picocli.CommandLine.HelpCommand;
import picocli.CommandLine.ITypeConverter;
import picocli.CommandLine.ScopeType;

import software.amazon.awssdk.regions.Region;

import software.amazon.awssdk.services.s3.S3Client;

import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
import software.amazon.awssdk.services.s3.model.ListObjectsV2Response;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.model.S3Object;

import software.amazon.awssdk.services.s3.paginators.ListObjectsV2Iterable;

/**
* Cooper ls command.
*
* @author Michael Heuer
*/
@Command(name = "ls")
public final class Ls implements Callable<Integer> {

@picocli.CommandLine.Option(
names = { "--region" },
type = Region.class,
converter = RegionConverter.class,
defaultValue = "us-west-2"
)
private Region region;

@picocli.CommandLine.Option(names = { "--human-readable" })
private boolean humanReadable;

@picocli.CommandLine.Option(names = { "--show-header" })
private boolean showHeader;

@picocli.CommandLine.Option(names = { "--reverse-columns" })
private boolean reverseColumns;

@picocli.CommandLine.Option(names = { "--verbose" })
private boolean verbose;

@picocli.CommandLine.Parameters(index = "0..*", arity = "1..*", descriptionKey = "uris")
private List<String> uris;

/** Logger. */
static Logger logger;

/** Human readable formatter. */
static final HumanReadableFormatter FORMATTER = new HumanReadableFormatter();

/** s3 bucket and prefix regex pattern. */
static final Pattern S3_URI = Pattern.compile("^s3:\\/\\/([a-zA-Z-]+)\\/*(.*)$");


@Override
public Integer call() throws Exception {

S3Client s3 = S3Client.builder()
.region(region)
.build();

if (showHeader) {
System.out.println(reverseColumns ? "size\turi" : "uri\tsize");
}

for (String uri : uris) {
Matcher m = S3_URI.matcher(uri);
if (m.matches()) {
String bucket = m.group(1);
String prefix = m.group(2);

logger.info("valid uri={} bucket={} prefix={}", uri, bucket, prefix);

ListObjectsV2Request.Builder requestBuilder = ListObjectsV2Request.builder()
.bucket(bucket);

if (prefix != null && prefix.trim().length() > 0) {
requestBuilder = requestBuilder.prefix(prefix);
}

ListObjectsV2Request request = requestBuilder.build();

logger.info("ListObjectsV2 request={}", request.toString());

ListObjectsV2Iterable responses = s3.listObjectsV2Paginator(request);
for (ListObjectsV2Response response : responses) {

logger.info("ListObjectsV2 response={}", response.toString());

for (S3Object content : response.contents()) {

String s3Path = "s3://" + bucket + "/" + content.key();

if (s3Path.startsWith(uri)) {

String size = humanReadable ? FORMATTER.format(content.size()) : String.valueOf(content.size());
System.out.println(reverseColumns ? size + "\t" + s3Path : s3Path + "\t" + size);
}
}
}
}
else {
logger.warn("uri {} not a valid s3 URI", uri);
}
}
return 0;
}


/**
* Main.
*
* @param args command line args
*/
public static void main(final String[] args) {

// cheat to set system property before initializing logger
if (Arrays.asList(args).contains("--verbose")) {
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info");
}
logger = LoggerFactory.getLogger(Cooper.class);

System.exit(new CommandLine(new Ls()).execute(args));
}
}
21 changes: 16 additions & 5 deletions src/main/resources/com/github/heuermh/cooper/Messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
usage.commandListHeading = %nCOMMANDS%n
usage.descriptionHeading = %n
usage.optionListHeading = %nOPTIONS%n
usage.parameterListHeading = %nPARAMETERS%n
usage.parameterListHeading =
usage.synopsisHeading = USAGE%n

usage.description.0 = List s3 paths recursively with content sizes.
usage.description.1 =
usage.description.2 = E.g.
usage.description.3 = \u0020 $ cat uris.txt | xargs coop
usage.description.4 = \u0020 $ coop s3://... | head -n 4
usage.description.5 = \u0020 $ coop s3://... | grep -m 10 -e '...'
usage.description.6 = \u0020 $ coop s3://... | cut -f 2 | sort -n -r
usage.description.3 = \u0020 $ cat uris.txt | xargs coop ls
usage.description.4 = \u0020 $ coop ls s3://... | head -n 4
usage.description.5 = \u0020 $ coop ls s3://... | grep -m 10 -e '...'
usage.description.6 = \u0020 $ coop ls s3://... | cut -f 2 | sort -n -r
usage.description.7 =

coop.help.usage.header = Display help information about coop.
Expand All @@ -25,3 +25,14 @@ region = AWS region, default US_WEST_2.
reverse-columns = Reverse the order of output columns.
uris = One or more s3 URIs.
verbose = Show additional logging messages.

coop.ls.usage.parameterListHeading = %nPARAMETERS%n

coop.ls.usage.description.0 = List s3 paths recursively with content sizes.
coop.ls.usage.description.1 =
coop.ls.usage.description.2 = E.g.
coop.ls.usage.description.3 = \u0020 $ cat uris.txt | xargs coop ls
coop.ls.usage.description.4 = \u0020 $ coop ls s3://... | head -n 4
coop.ls.usage.description.5 = \u0020 $ coop ls s3://... | grep -m 10 -e '...'
coop.ls.usage.description.6 = \u0020 $ coop ls s3://... | cut -f 2 | sort -n -r
coop.ls.usage.description.7 =

0 comments on commit 69cd801

Please sign in to comment.