Compare commits

...

24 Commits

Author SHA1 Message Date
Rob Greene
cebb3727b0 Adding an AppleSingle edit command. 2024-02-07 18:54:43 -06:00
Rob Greene
9d676a5f44 Updating Maven release notes with a copy from AppleCommander. 2023-10-28 12:00:24 -05:00
Rob Greene
45b57ce675 Bumping version: 1.2.2 2023-10-28 11:54:22 -05:00
Rob Greene
ab165c8953 Merge remote-tracking branch 'origin/master' 2023-10-28 11:49:14 -05:00
Rob Greene
eb6f080823 Upgrading PicoCLI version and adapting a bit. 2023-10-28 11:48:51 -05:00
A2 Geek
26b63b0277
Merge pull request #4 from AppleCommander/dependabot/gradle/junit-junit-4.13.2
Bump junit:junit from 4.13.1 to 4.13.2
2023-10-28 11:40:22 -05:00
A2 Geek
54416b5140
Adding automatic builds 2023-10-28 11:37:26 -05:00
dependabot[bot]
3f881a3467
Bump junit:junit from 4.13.1 to 4.13.2
Bumps [junit:junit](https://github.com/junit-team/junit4) from 4.13.1 to 4.13.2.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.13.1...r4.13.2)

---
updated-dependencies:
- dependency-name: junit:junit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-28 16:34:38 +00:00
A2 Geek
6b3090c9c3
Create dependabot.yml 2023-10-28 11:34:17 -05:00
Rob Greene
516521d39a Bumping to Gradle 7.3.1 (same as AppleCommander). #1 2023-10-28 11:30:54 -05:00
Rob Greene
ea58bc88f8 AppleSingle entry can have a length of 0. Adjusting how 0 byte entries
are reported.
2022-06-05 16:47:31 -05:00
Rob Greene
0651919ca0 AppleSingle entry can have a length of 0. Adjusting how 0 byte entries
are reported.
2022-06-05 16:47:21 -05:00
Rob Greene
0b1459931b AppleSingle entry can have a length of 0. Adjusting the 'analyze'
related edit.
2022-06-05 16:46:42 -05:00
Rob Greene
b21a7bb341 Enforcing Java 11. 2022-06-05 16:46:04 -05:00
Rob Greene
b39d3a7b56 Setting snapshot version. 2022-06-05 16:45:50 -05:00
Rob Greene
d689b5e0d5 First round of updates. Leaving publication for later. #1 2020-11-24 19:37:27 -06:00
Rob Greene
2bf7de8a0d Typo. 2018-06-19 20:10:11 -05:00
Rob Greene
369f1e5eba Cleaning up imports. 2018-06-07 21:21:27 -05:00
Rob Greene
05ab07e323 Adding some file testing capabilities; tweaked comments. 2018-06-06 21:56:42 -05:00
Rob Greene
153e0aa220 Adding AppleSingle.VERSION. 2018-06-04 22:20:13 -05:00
Rob Greene
549ce45c3c Displaying details on FileDatesInfo if present. 2018-06-04 22:19:53 -05:00
Rob Greene
d9df400d7f Bumping version. 2018-06-04 22:19:24 -05:00
Rob Greene
c1349bade8 Changing notation to (hopefully) be more clear on ranges. 2018-06-04 22:19:03 -05:00
Rob Greene
0f6fceaa55 Fixing spelling. 2018-06-04 22:18:27 -05:00
21 changed files with 659 additions and 236 deletions

11
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gradle" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

40
.github/workflows/gradle.yml vendored Normal file
View File

@ -0,0 +1,40 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
name: Build AppleSingle 'asu' CLI and API
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
with:
arguments: build
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: build-artifacts
path: tools/asu/build/libs/applesingle-tools-asu-*.jar
if-no-files-found: error

View File

@ -24,26 +24,35 @@ Extract secret key for the Gradle signing plugin:
$ gpg2 --export-secret-keys > secring.gpg
```
## Build and publish to Central Repository
## Gradle build and publish to Central Repository
> Note that all of this can be run from the main 'applesingle' folder.
> NOTE: The build has been updated to allow snapshots to be published. These appear to be automatically available.
Ensure full build passes:
```bash
$ ./gradlew clean test javadoc assemble
<...lots of stuff, primarily Javadoc issues...>
BUILD SUCCESSFUL in 3s
13 actionable tasks: 13 executed
./gradlew clean build
BUILD SUCCESSFUL in 8s
91 actionable tasks: 91 executed
```
Upload:
Publish:
```bash
$ ./gradlew uploadArchives
$ ./gradlew publish
BUILD SUCCESSFUL in 10s
10 actionable tasks: 1 executed, 9 up-to-date
BUILD SUCCESSFUL in 8s
2 actionable tasks: 2 executed
```
The can also be combined:
```bash
$ ./gradlew clean build publish
BUILD SUCCESSFUL in 16s
93 actionable tasks: 93 executed
```
Then follow "releasing the deployment" below.
@ -54,6 +63,7 @@ Just a reminder!
# References
* http://central.sonatype.org/pages/gradle.html
* http://central.sonatype.org/pages/gradle.html (NOTE: Documentation is out of date)
* http://central.sonatype.org/pages/releasing-the-deployment.html
* For all the other little pieces, Google is your friend. ;-)
* https://docs.gradle.org/current/userguide/publishing_maven.html
* For all the other little pieces, Google is your friend. ;-)

View File

@ -1,22 +1,43 @@
repositories {
jcenter()
plugins {
id 'java-library'
id 'maven-publish'
id 'signing'
}
apply plugin: 'java-library'
apply plugin: 'maven'
apply plugin: 'signing'
ext.isSnapshotVersion = version.endsWith("SNAPSHOT")
ext.isReleaseVersion = !ext.isSnapshotVersion
sourceCompatibility = '11'
targetCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'junit:junit:4.13.2'
}
tasks.withType(Jar) {
manifest {
attributes 'Implementation-Title': 'AppleSingle',
'Implementation-Version': "${project.version} (${new Date().format('yyyy-MM-dd HH:mm')})"
}
}
javadoc {
title = "applesingle-api ${project.version}"
source = sourceSets.main.allJava
options.addStringOption('Xdoclint:none', '-quiet')
}
task javadocJar(type: Jar) {
classifier = 'javadoc'
archiveClassifier = 'javadoc'
from javadoc
}
task sourcesJar(type: Jar) {
classifier = 'sources'
archiveClassifier = 'sources'
from sourceSets.main.allSource
}
@ -24,49 +45,52 @@ artifacts {
archives javadocJar, sourcesJar
}
signing {
// Only sign if we're uploading...
required { gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
authentication(userName: findProperty('ossrhUsername'), password: findProperty('ossrhPassword'))
}
snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
authentication(userName: findProperty('ossrhUsername'), password: findProperty('ossrhPassword'))
}
pom.project {
name archivesBaseName
packaging 'jar'
description 'A Java library for managing AppleSingle files.'
url 'https://applecommander.github.io/'
scm {
url 'https://github.com/AppleCommander/applesingle'
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
artifact sourcesJar
artifact javadocJar
pom {
groupId = "net.sf.applecommander"
artifactId = "applesingle-api"
name = 'applesingle-api'
description = 'A Java library for managing AppleSingle files.'
url = 'https://applecommander.github.io/'
licenses {
license {
name = 'The GNU General Public License (GPL) Version 2, June 1991'
url = 'https://www.gnu.org/licenses/gpl-2.0.html'
}
}
developers {
developer {
id = 'robgreene'
name = 'Rob Greene'
email = 'robgreene@gmail.com'
}
}
scm {
connection = 'scm:git:https://github.com/AppleCommander/applesingle.git'
developerConnection = 'scm:git:git@github.com:AppleCommander/applesingle.git'
url = 'https://github.com/AppleCommander/applesingle'
}
}
repositories {
maven {
def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2"
def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
url = isSnapshotVersion ? snapshotsRepoUrl : releasesRepoUrl
credentials {
username = findProperty('ossrhUsername')
password = findProperty('ossrhPassword')
}
}
}
}
licenses {
license {
name 'The GNU General Public License (GPL) Version 3, 29 June 2007'
url 'https://www.gnu.org/licenses/gpl-3.0.html'
}
}
developers {
developer {
id 'robgreene'
email 'robgreene@gmail.com'
}
}
}
}
}
}
signing {
sign publishing.publications.mavenJava
}

View File

@ -20,7 +20,7 @@ import java.util.function.Consumer;
/**
* Support reading of data from and AppleSingle source.
* Does not implement all components at this time, extend as required.
* Does not implement all components at this time, extend as required and/or understood.
* All construction has been deferred to the <code>read(...)</code> or {@link #builder()} methods.
* <p>
* Currently supports entries:<br/>
@ -37,6 +37,11 @@ public class AppleSingle {
public static final int VERSION_NUMBER1 = 0x00010000;
public static final int VERSION_NUMBER2 = 0x00020000;
public static final String VERSION;
static {
VERSION = AppleSingle.class.getPackage().getImplementationVersion();
}
private Map<Integer,Consumer<Entry>> entryConsumers = new HashMap<>();
{
entryConsumers.put(1, entry -> this.dataFork = entry.getData());
@ -80,11 +85,12 @@ public class AppleSingle {
return fileDatesInfo;
}
/** Write this AppleSingle to the given output stream. Note that it only supports the "understood" components. */
public void save(OutputStream outputStream) throws IOException {
List<Entry> entries = new ArrayList<>();
Optional.ofNullable(this.realName)
.map(String::getBytes)
.map(b -> Entry.create(EntryType.REAL_NAME, b))
.map(Entry::realName)
.ifPresent(entries::add);
Optional.ofNullable(this.prodosFileInfo)
.map(ProdosFileInfo::toEntry)
@ -93,18 +99,20 @@ public class AppleSingle {
.map(FileDatesInfo::toEntry)
.ifPresent(entries::add);
Optional.ofNullable(this.resourceFork)
.map(b -> Entry.create(EntryType.RESOURCE_FORK, b))
.map(Entry::resourceFork)
.ifPresent(entries::add);
Optional.ofNullable(this.dataFork)
.map(b -> Entry.create(EntryType.DATA_FORK, b))
.map(Entry::dataFork)
.ifPresent(entries::add);
write(outputStream, entries);
}
/** Save this AppleSingle to a File. */
public void save(File file) throws IOException {
try (FileOutputStream outputStream = new FileOutputStream(file)) {
save(outputStream);
}
}
/** Save this AppleSingle to a Path. */
public void save(Path path) throws IOException {
try (OutputStream outputStream = Files.newOutputStream(path)) {
save(outputStream);
@ -112,8 +120,8 @@ public class AppleSingle {
}
/**
* Common write capability for an AppleSingle. Also can be used by external entities to
* write a properly formatted AppleSingle file without the ProDOS assumptions of AppleSingle.
* Common write capability for an AppleSingle based on entries. Also can be used by external
* entities to write a properly formatted AppleSingle file without the ProDOS assumptions of AppleSingle.
*/
public static void write(OutputStream outputStream, List<Entry> entries) throws IOException {
final byte[] filler = new byte[16];
@ -194,11 +202,58 @@ public class AppleSingle {
message, String.join(",", versions), actual));
}
/** Perform a quick test against a File to see if it is an AppleSingle file. */
public static boolean test(File file) throws IOException {
Objects.requireNonNull(file);
return test(file.toPath());
}
/** Perform a quick test against a Path to see if it is an AppleSingle file. */
public static boolean test(Path path) throws IOException {
Objects.requireNonNull(path);
return test(Files.readAllBytes(path));
}
/** Perform a quick test against an InputStream to see if it is an AppleSingle file. */
public static boolean test(InputStream inputStream) throws IOException {
Objects.requireNonNull(inputStream);
return test(Utilities.toByteArray(inputStream));
}
/** Perform a quick test against a byte array to see if it is an AppleSingle file. */
public static boolean test(byte[] data) {
Objects.requireNonNull(data);
return test(AppleSingleReader.builder(data).build());
}
/** Perform a quick test against a reader to see if it is an AppleSingle file. */
public static boolean test(AppleSingleReader reader) {
Objects.requireNonNull(reader);
return check(reader, MAGIC_NUMBER) && check(reader, VERSION_NUMBER1, VERSION_NUMBER2);
}
private static boolean check(AppleSingleReader reader, int... expecteds) {
try {
final String message = ""; // Just needed for read.
int actual = reader.read(Integer.BYTES, message).getInt();
for (int expected : expecteds) {
if (actual == expected) return true;
}
} catch (ArrayIndexOutOfBoundsException ignored) {
// Bad file! Fall through.
}
return false;
}
public static Builder builder() {
return new Builder();
}
public static Builder builder(AppleSingle original) {
return new Builder(original);
}
public static class Builder {
private AppleSingle as = new AppleSingle();
final private AppleSingle as;
private Builder() {
this.as = new AppleSingle();
}
private Builder(AppleSingle original) {
this.as = original;
}
public Builder realName(String realName) {
if (!Character.isAlphabetic(realName.charAt(0))) {
throw new IllegalArgumentException("ProDOS file names must begin with a letter");

View File

@ -86,7 +86,7 @@ public final class AppleSingleReader {
/**
* A reporter for the {@code AppleSingleReader#readAt(int, int, String)} method,
* heaviliy modeled on the {@code Consumer} interface.
* heavily modeled on the {@code Consumer} interface.
*/
public interface ReadAtReporter {
/**

View File

@ -15,7 +15,8 @@ public class Entry {
private int offset;
private int length;
private byte[] data;
/** Create an Entry and read it's data from the reader. */
public static Entry create(AppleSingleReader reader) {
Objects.requireNonNull(reader);
@ -28,6 +29,7 @@ public class Entry {
entry.data = reader.readAt(entry.offset, entry.length, EntryType.findNameOrUnknown(entry)).array();
return entry;
}
/** Create an Entry. */
public static Entry create(EntryType type, byte[] data) {
Objects.requireNonNull(type);
Objects.requireNonNull(data);
@ -38,6 +40,18 @@ public class Entry {
entry.data = data;
return entry;
}
/** Create a REAL_NAME entry. Primarily used for Java 8 streams. */
public static Entry realName(byte[] data) {
return create(EntryType.REAL_NAME, data);
}
/** Create a DATA_FORK entry. Primarily used for Java 8 streams. */
public static Entry dataFork(byte[] data) {
return create(EntryType.DATA_FORK, data);
}
/** Create a RESOURCE_FORK entry. Primarily used for Java 8 streams. */
public static Entry resourceFork(byte[] data) {
return create(EntryType.RESOURCE_FORK, data);
}
public int getEntryId() {
return entryId;

View File

@ -2,8 +2,10 @@ package io.github.applecommander.applesingle;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -80,6 +82,17 @@ public class AppleSingleTest {
@Test(expected = IllegalArgumentException.class)
public void testProdosFileNameFirstCharacter() {
// Fails due to the first character being a digit.
AppleSingle.builder().realName("1st-file").build();
}
@Test
public void testTest() throws IOException {
// Known valid
assertTrue(AppleSingle.test(getClass().getResourceAsStream(AS_HELLO_BIN)));
// Known invalid
assertFalse(AppleSingle.test(new byte[200]));
// Could/should generate error due to truncated data, but this method should just give us a false.
assertFalse(AppleSingle.test(new byte[3]));
}
}

View File

@ -1,7 +0,0 @@
if (JavaVersion.current().isJava8Compatible()) {
allprojects {
tasks.withType(Javadoc) {
options.addStringOption('Xdoclint:none', '-quiet')
}
}
}

View File

@ -1,7 +1,7 @@
# Universal applesingle version number. Used for:
# - Naming JAR file.
# - The build will insert this into a file that is read at run time as well.
version=1.2.0
version=1.3.0
# Maven Central Repository G and A of GAV coordinate. :-)
group=net.sf.applecommander

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip

286
gradlew vendored
View File

@ -1,78 +1,129 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed 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
#
# https://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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@ -89,84 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

43
gradlew.bat vendored
View File

@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -45,28 +64,14 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell

View File

@ -1,25 +1,30 @@
plugins {
id 'org.springframework.boot' version '2.0.2.RELEASE'
id 'org.springframework.boot' version '2.6.1'
id 'java'
id 'application'
}
sourceCompatibility = '11'
targetCompatibility = '11'
repositories {
jcenter()
}
apply plugin: 'application'
mainClassName = "io.github.applecommander.applesingle.tools.asu.Main"
bootJar {
manifest {
attributes(
'Implementation-Title': 'applesingle',
'Implementation-Version': "${version} (${new Date().format('yyyy-MM-dd HH:mm')})"
)
}
mavenCentral()
}
dependencies {
compile 'info.picocli:picocli:3.0.2'
compile project(':applesingle-api')
implementation 'info.picocli:picocli:4.7.5'
implementation project(':applesingle-api')
}
application {
mainClass = "io.github.applecommander.applesingle.tools.asu.Main"
}
bootJar {
manifest {
attributes(
'Implementation-Title': 'applesingle',
'Implementation-Version': "${project.version} (${new Date().format('yyyy-MM-dd HH:mm')})"
)
}
}

View File

@ -0,0 +1,167 @@
package io.github.applecommander.applesingle.tools.asu;
import io.github.applecommander.applesingle.AppleSingle;
import io.github.applecommander.applesingle.Utilities;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.Callable;
/**
* Supports editing of AppleSingle archives.
*/
@Command(name = "edit", description = { "Edit an AppleSingle file" },
parameterListHeading = "%nParameters:%n",
descriptionHeading = "%n",
footerHeading = "%nNotes:%n",
footer = { "* Dates should be supplied like '2007-12-03T10:15:30.00Z'.",
"* 'Known' ProDOS file types: TXT, BIN, INT, BAS, REL, SYS.",
"* Include the output file or specify stdout" },
optionListHeading = "%nOptions:%n")
public class EditCommand implements Callable<Void> {
@Option(names = { "-h", "--help" }, description = "Show help for subcommand", usageHelp = true)
private boolean helpFlag;
@Option(names = "--stdin", description = "Read AppleSingle file from stdin")
private boolean stdinFlag;
@Option(names = "--stdout", description = "Write AppleSingle file to stdout")
private boolean stdoutFlag;
@Option(names = "--stdin-fork", description = "Read fork from stdin (specify data or resource)")
private ForkType stdinForkType;
@Option(names = "--fix-text", description = "Set the high bit and fix line endings")
private boolean fixTextFlag;
@Option(names = "--data-fork", description = "Read data fork from file")
private Path dataForkFile;
@Option(names = "--resource-fork", description = "Read resource fork from file")
private Path resourceForkFile;
@Option(names = "--name", description = "Set the filename (defaults to name of data fork, if supplied)")
private String realName;
@Option(names = "--access", description = "Set the ProDOS access flags", converter = IntegerTypeConverter.class)
private Integer access;
@Option(names = "--filetype", description = "Set the ProDOS file type", converter = ProdosFileTypeConverter.class)
private Integer filetype;
@Option(names = "--auxtype", description = "Set the ProDOS auxtype", converter = IntegerTypeConverter.class)
private Integer auxtype;
@Option(names = "--creation-date", description = "Set the file creation date")
private Instant creationDate;
@Option(names = "--modification-date", description = "Set the file modification date")
private Instant modificationDate;
@Option(names = "--backup-date", description = "Set the file backup date")
private Instant backupDate;
@Option(names = "--access-date", description = "Set the file access date")
private Instant accessDate;
@Parameters(arity = "0..1", description = "AppleSingle file to modify")
private Path file;
@Override
public Void call() throws IOException {
validateArguments();
AppleSingle original = stdinFlag ? AppleSingle.read(System.in) : AppleSingle.read(file);
byte[] dataFork = prepDataFork();
byte[] resourceFork = prepResourceFork();
AppleSingle applesingle = buildAppleSingle(original, dataFork, resourceFork);
writeAppleSingle(applesingle);
return null;
}
public void validateArguments() throws IOException {
if ((stdinFlag && file != null) || (!stdinFlag && file == null)) {
throw new IOException("Please choose one of stdin or input file for original");
}
if ((dataForkFile != null && stdinForkType == ForkType.data)
|| (resourceForkFile != null && stdinForkType == ForkType.resource)) {
throw new IOException("Stdin only supports one type of fork for input");
}
if (stdinForkType == ForkType.both) {
throw new IOException("Unable to read two forks from stdin");
}
}
public byte[] prepDataFork() throws IOException {
byte[] dataFork = null;
if (stdinForkType == ForkType.data) {
dataFork = Utilities.toByteArray(System.in);
} else if (dataForkFile != null) {
dataFork = Files.readAllBytes(dataForkFile);
}
if (fixTextFlag && dataFork != null) {
for (int i=0; i<dataFork.length; i++) {
if (dataFork[i] == '\n') dataFork[i] = 0x0d;
dataFork[i] = (byte)(dataFork[i] | 0x80);
}
}
return dataFork;
}
public byte[] prepResourceFork() throws IOException {
byte[] resourceFork = null;
if (stdinForkType == ForkType.resource) {
resourceFork = Utilities.toByteArray(System.in);
} else if (resourceForkFile != null) {
resourceFork = Files.readAllBytes(resourceForkFile);
}
return resourceFork;
}
public AppleSingle buildAppleSingle(AppleSingle original,
byte[] dataFork,
byte[] resourceFork) throws IOException {
AppleSingle.Builder builder = AppleSingle.builder(original);
if (realName != null) {
builder.realName(realName);
} else if (dataForkFile != null) {
String name = dataForkFile.getFileName().toString();
builder.realName(name);
}
if (access != null) builder.access(access.intValue());
if (filetype != null) builder.fileType(filetype.intValue());
if (auxtype != null) builder.auxType(auxtype.intValue());
if (dataFork != null) builder.dataFork(dataFork);
if (resourceFork != null) builder.resourceFork(resourceFork);
if (dataForkFile != null || resourceForkFile != null) {
Path path = Optional.ofNullable(dataForkFile).orElse(resourceForkFile);
BasicFileAttributes attribs = Files.readAttributes(path, BasicFileAttributes.class);
builder.creationDate(attribs.creationTime().toInstant());
builder.modificationDate(attribs.lastModifiedTime().toInstant());
builder.accessDate(attribs.lastAccessTime().toInstant());
}
if (creationDate != null) builder.creationDate(creationDate);
if (modificationDate != null) builder.modificationDate(modificationDate);
if (backupDate != null) builder.backupDate(backupDate);
if (accessDate != null) builder.accessDate(accessDate);
return builder.build();
}
public void writeAppleSingle(AppleSingle applesingle) throws IOException {
if (stdoutFlag) {
applesingle.save(System.out);
} else {
applesingle.save(file);
System.out.printf("Saved to '%s'.\n", file);
}
}
}

View File

@ -32,6 +32,9 @@ public class HexDumper {
description = ""; // Only on first line!
offset += line.length;
}
if (data.length == 0) {
printLine.print(address+offset, data, String.format("%s (empty)", description));
}
}
public void standardLine(int address, byte[] data, String description) {
@ -48,7 +51,7 @@ public class HexDumper {
char ch = ' ';
if (i < data.length) {
byte b = data[i];
ch = (b >= ' ' && Byte.toUnsignedInt(b) != 0xff) ? (char)b : '.';
ch = (b >= ' ' && Byte.toUnsignedInt(b) < 0x7f) ? (char)b : '.';
}
ps.printf("%c", ch);
}

View File

@ -6,6 +6,7 @@ import java.util.Optional;
import java.util.concurrent.Callable;
import io.github.applecommander.applesingle.AppleSingle;
import io.github.applecommander.applesingle.FileDatesInfo;
import io.github.applecommander.applesingle.ProdosFileInfo;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@ -32,7 +33,9 @@ public class InfoCommand implements Callable<Void> {
@Override
public Void call() throws IOException {
AppleSingle applesingle = stdinFlag ? AppleSingle.read(System.in) : AppleSingle.read(file);
System.out.printf("Real Name: %s\n", Optional.ofNullable(applesingle.getRealName()).orElse("-Unknown-"));
System.out.printf("ProDOS info:\n");
if (applesingle.getProdosFileInfo() == null) {
System.out.println(" Not supplied.");
@ -42,7 +45,20 @@ public class InfoCommand implements Callable<Void> {
System.out.printf(" File Type: 0x%02X\n", prodosFileInfo.getFileType());
System.out.printf(" Auxtype: 0x%04X\n", prodosFileInfo.getAuxType());
}
System.out.printf("File dates info:\n");
if (applesingle.getFileDatesInfo() == null) {
System.out.println(" Not supplied.");
} else {
FileDatesInfo fileDatesInfo = applesingle.getFileDatesInfo();
System.out.printf(" Creation: %s\n", fileDatesInfo.getCreationInstant());
System.out.printf(" Modification: %s\n", fileDatesInfo.getModificationInstant());
System.out.printf(" Access: %s\n", fileDatesInfo.getAccessInstant());
System.out.printf(" Backup: %s\n", fileDatesInfo.getBackupInstant());
}
System.out.printf("Data Fork: Present, %,d bytes\n", applesingle.getDataFork().length);
System.out.printf("Resource Fork: %s\n",
Optional.ofNullable(applesingle.getResourceFork())
.map(d -> String.format("Present, %,d bytes", d.length))

View File

@ -17,7 +17,7 @@ public class IntRange {
/** Create an integer range. */
public static IntRange of(int low, int high) {
if (low == high) throw new UnsupportedOperationException("low and high cannot be the same");
if (low > high) throw new UnsupportedOperationException("low cannot be greater than high");
return new IntRange(Math.min(low,high), Math.max(low,high));
}
/** Normalize a list by combining all integer ranges that match. */
@ -61,6 +61,6 @@ public class IntRange {
}
@Override
public String toString() {
return String.format("[%d..%d)", low, high);
return String.format("%d..%d", low, high-1);
}
}

View File

@ -17,19 +17,20 @@ import picocli.CommandLine.Option;
description = "AppleSingle utility",
subcommands = {
AnalyzeCommand.class,
CreateCommand.class,
CreateCommand.class,
EditCommand.class,
ExtractCommand.class,
FilterCommand.class,
HelpCommand.class,
InfoCommand.class,
})
public class Main implements Runnable {
@Option(names = "--debug", description = "Dump full stack trackes if an error occurs")
@Option(names = "--debug", description = "Dump full stack traces if an error occurs")
private static boolean debugFlag;
public static void main(String[] args) {
try {
CommandLine.run(new Main(), args);
new CommandLine(new Main()).execute(args);
} catch (Throwable t) {
if (Main.debugFlag) {
t.printStackTrace(System.err);

View File

@ -1,10 +1,14 @@
package io.github.applecommander.applesingle.tools.asu;
import io.github.applecommander.applesingle.AppleSingle;
import picocli.CommandLine.IVersionProvider;
/** Display version information. Note that this is dependent on the Spring Boot Gradle plugin configuration. */
public class VersionProvider implements IVersionProvider {
public String[] getVersion() {
return new String[] { Main.class.getPackage().getImplementationVersion() };
return new String[] {
String.format("CLI: %s", Main.class.getPackage().getImplementationVersion()),
String.format("API: %s", AppleSingle.VERSION)
};
}
}