Opinionated java-library project template

feat: update script, more flags

kokirigla.de 12f0d485 b4e28764

verified
+176 -131
+116 -67
.scripts/rename-template.sh
··· 2 2 set -euo pipefail 3 3 4 4 usage() { 5 - cat <<'EOF' 6 - Usage: ./scripts/rename-template.sh -g <groupId> -p <projectName> 5 + cat <<'USAGE' 6 + Usage: ./.scripts/rename-template.sh \ 7 + -g <groupId> \ 8 + -p <projectName> \ 9 + -n <snapshotsRepoName> \ 10 + -s <snapshotsRepoUrl> \ 11 + -N <releasesRepoName> \ 12 + -r <releasesRepoUrl> \ 13 + -a <author> \ 14 + -y <year> 7 15 8 - Examples: 9 - ./scripts/rename-template.sh -g com.acme -p my-project 10 - EOF 16 + Example: 17 + ./.scripts/rename-template.sh \ 18 + -g com.acme \ 19 + -p my-project \ 20 + -n acme-snapshots \ 21 + -s https://repo.acme.com/snapshots \ 22 + -N acme-releases \ 23 + -r https://repo.acme.com/releases \ 24 + -a "Acme, Inc." \ 25 + -y 2026 26 + USAGE 11 27 } 12 28 13 29 GROUP_ID="" 14 30 PROJECT_NAME="" 15 - PACKAGE_NAME="" 31 + SNAPSHOTS_REPO_NAME="" 32 + SNAPSHOTS_REPO_URL="" 33 + RELEASES_REPO_NAME="" 34 + RELEASES_REPO_URL="" 35 + AUTHOR="" 36 + COPYRIGHT_YEAR="" 16 37 17 - while getopts ":g:p:h" opt; do 38 + while getopts ":g:p:n:s:N:r:a:y:h" opt; do 18 39 case "$opt" in 19 40 g) GROUP_ID="$OPTARG" ;; 20 41 p) PROJECT_NAME="$OPTARG" ;; 42 + n) SNAPSHOTS_REPO_NAME="$OPTARG" ;; 43 + s) SNAPSHOTS_REPO_URL="$OPTARG" ;; 44 + N) RELEASES_REPO_NAME="$OPTARG" ;; 45 + r) RELEASES_REPO_URL="$OPTARG" ;; 46 + a) AUTHOR="$OPTARG" ;; 47 + y) COPYRIGHT_YEAR="$OPTARG" ;; 21 48 h) 22 49 usage 23 50 exit 0 ··· 35 62 esac 36 63 done 37 64 38 - if [[ -z "$GROUP_ID" || -z "$PROJECT_NAME" ]]; then 65 + if [[ -z "$GROUP_ID" || -z "$PROJECT_NAME" || -z "$SNAPSHOTS_REPO_NAME" || -z "$SNAPSHOTS_REPO_URL" || -z "$RELEASES_REPO_NAME" || -z "$RELEASES_REPO_URL" || -z "$AUTHOR" || -z "$COPYRIGHT_YEAR" ]]; then 39 66 usage 40 67 exit 1 41 68 fi ··· 58 85 echo "$normalized" 59 86 } 60 87 61 - package_segments=() 62 - IFS='.' read -r -a group_parts <<<"$GROUP_ID" 63 - for part in "${group_parts[@]}"; do 64 - package_segments+=("$(normalize_segment "$part")") 65 - done 66 - package_segments+=("$(normalize_segment "$PROJECT_NAME")") 67 - PACKAGE_NAME="$(IFS='.'; echo "${package_segments[*]}")" 88 + build_package_name() { 89 + local group_id="$1" 90 + local project_name="$2" 91 + local -a package_segments 92 + local -a group_parts 93 + 94 + IFS='.' read -r -a group_parts <<<"$group_id" 95 + for part in "${group_parts[@]}"; do 96 + package_segments+=("$(normalize_segment "$part")") 97 + done 98 + package_segments+=("$(normalize_segment "$project_name")") 99 + IFS='.' 100 + echo "${package_segments[*]}" 101 + } 68 102 103 + PACKAGE_NAME="$(build_package_name "$GROUP_ID" "$PROJECT_NAME")" 69 104 REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" 70 105 TEMPLATE_PACKAGE="com.example.template" 71 - TEMPLATE_GROUP="com.example" 106 + 107 + SOURCE_MODULE_DIR="$REPO_ROOT/template-api" 72 108 73 109 FILES=( 74 110 "$REPO_ROOT/build.gradle.kts" 75 111 "$REPO_ROOT/settings.gradle.kts" 76 - "$REPO_ROOT/readme.md" 77 112 "$REPO_ROOT/build-logic/src/main/kotlin/extensions.kt" 113 + "$REPO_ROOT/build-logic/src/main/kotlin/publishing-conventions.gradle.kts" 114 + "$REPO_ROOT/license.txt" 115 + "$REPO_ROOT/license_header.txt" 78 116 ) 79 117 80 - if [[ -d "$REPO_ROOT/api/src/main/java" ]]; then 118 + if [[ -d "$SOURCE_MODULE_DIR/src/main/java" ]]; then 81 119 while IFS= read -r -d '' file; do 82 120 FILES+=("$file") 83 - done < <(find "$REPO_ROOT/api/src/main/java" -type f -print0) 121 + done < <(find "$SOURCE_MODULE_DIR/src/main/java" -type f -print0) 84 122 fi 85 123 86 - if [[ -d "$REPO_ROOT/api/src/test/java" ]]; then 124 + if [[ -d "$SOURCE_MODULE_DIR/src/test/java" ]]; then 87 125 while IFS= read -r -d '' file; do 88 126 FILES+=("$file") 89 - done < <(find "$REPO_ROOT/api/src/test/java" -type f -print0) 127 + done < <(find "$SOURCE_MODULE_DIR/src/test/java" -type f -print0) 90 128 fi 91 129 92 130 SED_INPLACE=(-i) ··· 94 132 SED_INPLACE=(-i '') 95 133 fi 96 134 97 - replace_in_file() { 135 + escape_sed() { 136 + local value="$1" 137 + value="${value//\\/\\\\}" 138 + value="${value//&/\\&}" 139 + value="${value//\//\\/}" 140 + echo "$value" 141 + } 142 + 143 + replace_placeholders() { 98 144 local file="$1" 99 - local safe_package safe_group safe_project 100 - safe_package="${PACKAGE_NAME//\\/\\\\}" 101 - safe_package="${safe_package//&/\\&}" 102 - safe_package="${safe_package//\//\\/}" 103 - safe_group="${GROUP_ID//\\/\\\\}" 104 - safe_group="${safe_group//&/\\&}" 105 - safe_group="${safe_group//\//\\/}" 106 - safe_project="${PROJECT_NAME//\\/\\\\}" 107 - safe_project="${safe_project//&/\\&}" 108 - safe_project="${safe_project//\//\\/}" 145 + local safe_group safe_project safe_package 146 + local safe_snapshots_repo_name safe_snapshots_repo_url 147 + local safe_releases_repo_name safe_releases_repo_url 148 + local safe_project_api 149 + local safe_author safe_copyright_year 150 + 151 + safe_group="$(escape_sed "$GROUP_ID")" 152 + safe_project="$(escape_sed "$PROJECT_NAME")" 153 + safe_package="$(escape_sed "$PACKAGE_NAME")" 154 + safe_snapshots_repo_name="$(escape_sed "$SNAPSHOTS_REPO_NAME")" 155 + safe_snapshots_repo_url="$(escape_sed "$SNAPSHOTS_REPO_URL")" 156 + safe_releases_repo_name="$(escape_sed "$RELEASES_REPO_NAME")" 157 + safe_releases_repo_url="$(escape_sed "$RELEASES_REPO_URL")" 158 + safe_project_api="$(escape_sed "${PROJECT_NAME}-api")" 159 + safe_author="$(escape_sed "$AUTHOR")" 160 + safe_copyright_year="$(escape_sed "$COPYRIGHT_YEAR")" 161 + 109 162 sed "${SED_INPLACE[@]}" \ 110 - -e "s/com\.example\.template/$safe_package/g" \ 111 - -e "s/com\.example/$safe_group/g" \ 112 - -e "s/template/$safe_project/g" \ 163 + -e "s/{{GROUP_ID}}/$safe_group/g" \ 164 + -e "s/{{PROJECT_NAME}}/$safe_project/g" \ 165 + -e "s/{{PACKAGE_NAME}}/$safe_package/g" \ 166 + -e "s/{{SNAPSHOTS_REPO_NAME}}/$safe_snapshots_repo_name/g" \ 167 + -e "s/{{SNAPSHOTS_REPO_URL}}/$safe_snapshots_repo_url/g" \ 168 + -e "s/{{RELEASES_REPO_NAME}}/$safe_releases_repo_name/g" \ 169 + -e "s/{{RELEASES_REPO_URL}}/$safe_releases_repo_url/g" \ 170 + -e "s/{{AUTHOR}}/$safe_author/g" \ 171 + -e "s/{{COPYRIGHT_YEAR}}/$safe_copyright_year/g" \ 172 + -e "s/template-api/$safe_project_api/g" \ 113 173 "$file" 114 174 } 115 175 116 176 for file in "${FILES[@]}"; do 117 177 [[ -f "$file" ]] || continue 118 - replace_in_file "$file" 178 + replace_placeholders "$file" 119 179 echo "Updated: $file" 120 180 done 121 181 122 - OLD_PACKAGE_DIR="$REPO_ROOT/api/src/main/java/$(echo "$TEMPLATE_PACKAGE" | tr '.' '/')" 123 - if [[ -d "$OLD_PACKAGE_DIR" ]]; then 124 - NEW_PACKAGE_DIR="$REPO_ROOT/api/src/main/java/$(echo "$PACKAGE_NAME" | tr '.' '/')" 125 - mkdir -p "$NEW_PACKAGE_DIR" 126 - shopt -s dotglob nullglob 127 - mv "$OLD_PACKAGE_DIR"/* "$NEW_PACKAGE_DIR"/ 128 - shopt -u dotglob nullglob 129 - rmdir "$OLD_PACKAGE_DIR" 2>/dev/null || true 182 + move_package_dir() { 183 + local base_dir="$1" 184 + local old_dir 185 + local new_dir 186 + old_dir="$base_dir/$(echo "$TEMPLATE_PACKAGE" | tr '.' '/')" 187 + new_dir="$base_dir/$(echo "$PACKAGE_NAME" | tr '.' '/')" 130 188 131 - OLD_GROUP_DIR="$REPO_ROOT/api/src/main/java/$(echo "$TEMPLATE_GROUP" | tr '.' '/')" 132 - if [[ -d "$OLD_GROUP_DIR" && -z "$(find "$OLD_GROUP_DIR" -type f -o -type d | tail -n 1)" ]]; then 133 - rmdir "$OLD_GROUP_DIR" 2>/dev/null || true 189 + if [[ -d "$old_dir" ]]; then 190 + mkdir -p "$new_dir" 191 + shopt -s dotglob nullglob 192 + mv "$old_dir"/* "$new_dir"/ 193 + shopt -u dotglob nullglob 194 + rmdir "$old_dir" 2>/dev/null || true 134 195 fi 135 - fi 136 196 137 - if [[ -d "$REPO_ROOT/api/src/main/java" ]]; then 138 - find "$REPO_ROOT/api/src/main/java" -type d -empty -delete 139 - fi 140 - 141 - OLD_TEST_PACKAGE_DIR="$REPO_ROOT/api/src/test/java/$(echo "$TEMPLATE_PACKAGE" | tr '.' '/')" 142 - if [[ -d "$OLD_TEST_PACKAGE_DIR" ]]; then 143 - NEW_TEST_PACKAGE_DIR="$REPO_ROOT/api/src/test/java/$(echo "$PACKAGE_NAME" | tr '.' '/')" 144 - mkdir -p "$NEW_TEST_PACKAGE_DIR" 145 - shopt -s dotglob nullglob 146 - mv "$OLD_TEST_PACKAGE_DIR"/* "$NEW_TEST_PACKAGE_DIR"/ 147 - shopt -u dotglob nullglob 148 - rmdir "$OLD_TEST_PACKAGE_DIR" 2>/dev/null || true 197 + if [[ -d "$base_dir" ]]; then 198 + find "$base_dir" -type d -empty -delete 199 + fi 200 + } 149 201 150 - OLD_TEST_GROUP_DIR="$REPO_ROOT/api/src/test/java/$(echo "$TEMPLATE_GROUP" | tr '.' '/')" 151 - if [[ -d "$OLD_TEST_GROUP_DIR" && -z "$(find "$OLD_TEST_GROUP_DIR" -type f -o -type d | tail -n 1)" ]]; then 152 - rmdir "$OLD_TEST_GROUP_DIR" 2>/dev/null || true 153 - fi 154 - fi 202 + move_package_dir "$SOURCE_MODULE_DIR/src/main/java" 203 + move_package_dir "$SOURCE_MODULE_DIR/src/test/java" 155 204 156 - if [[ -d "$REPO_ROOT/api/src/test/java" ]]; then 157 - find "$REPO_ROOT/api/src/test/java" -type d -empty -delete 205 + if [[ -d "$REPO_ROOT/template-api" ]]; then 206 + mv "$REPO_ROOT/template-api" "$REPO_ROOT/${PROJECT_NAME}-api" 158 207 fi 159 208 160 209 echo "Done. GroupId=$GROUP_ID ProjectName=$PROJECT_NAME PackageName=$PACKAGE_NAME"
api/build.gradle.kts template-api/build.gradle.kts
api/src/main/java/com/example/template/api/.gitkeep template-api/src/main/java/com/example/template/api/.gitkeep
-39
api/src/test/java/com/example/template/TemplateTest.java
··· 1 - /* 2 - * This file is part of <project name>, licensed under the MIT License. 3 - * 4 - * Copyright (c) 2026 nayrid 5 - * 6 - * Permission is hereby granted, free of charge, to any person obtaining a copy 7 - * of this software and associated documentation files (the "Software"), to deal 8 - * in the Software without restriction, including without limitation the rights 9 - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 - * copies of the Software, and to permit persons to whom the Software is 11 - * furnished to do so, subject to the following conditions: 12 - * 13 - * The above copyright notice and this permission notice shall be included in all 14 - * copies or substantial portions of the Software. 15 - * 16 - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 - * SOFTWARE. 23 - */ 24 - package com.example.template; 25 - 26 - import static org.junit.jupiter.api.Assertions.assertTrue; 27 - 28 - import org.jspecify.annotations.NullMarked; 29 - import org.junit.jupiter.api.Test; 30 - 31 - @NullMarked 32 - class TemplateTest { 33 - 34 - @Test 35 - void testTemplate() { 36 - assertTrue(true); 37 - } 38 - 39 - }
+1 -3
build-logic/src/main/kotlin/extensions.kt
··· 11 11 * Relocate a package into a project-scoped namespace. 12 12 */ 13 13 fun ShadowJar.relocateDependency(pkg: String) { 14 - val groupId = project.group.toString().ifBlank { "com.example" } 15 - val projectId = project.name.ifBlank { "template" } 16 - relocate(pkg, "$groupId.$projectId.libs.$pkg") 14 + relocate(pkg, "${project.group}.${project.name}.libs.$pkg") 17 15 }
+7 -12
build-logic/src/main/kotlin/publishing-conventions.gradle.kts
··· 1 1 plugins { 2 2 id("base-conventions") 3 - `maven-publish` 3 + id("net.kyori.indra.publishing") 4 + } 5 + 6 + indra { 7 + publishSnapshotsTo("{{SNAPSHOTS_REPO_NAME}}", "{{SNAPSHOTS_REPO_URL}}") 8 + publishReleasesTo("{{RELEASES_REPO_NAME}}", "{{RELEASES_REPO_URL}}") 4 9 } 5 10 6 11 tasks { ··· 11 16 encoding = Charsets.UTF_8.name() 12 17 links( 13 18 "https://docs.oracle.com/en/java/javase/25/docs/api/", 19 + "https://jspecify.dev/docs/api/", 14 20 ) 15 21 } 16 22 } 17 23 } 18 24 } 19 - 20 - configure<PublishingExtension> { 21 - publications { 22 - register<MavenPublication>("maven") { 23 - from(components["java"]) 24 - } 25 - } 26 - repositories { 27 - mavenLocal() 28 - } 29 - }
+2 -2
build.gradle.kts
··· 1 - group = "com.example" 2 - version = "1.0.0" 1 + group = "{{GROUP_ID}}" 2 + version = "1.0.0-SNAPSHOT"
+1 -1
license.txt
··· 1 1 MIT License 2 2 3 - Copyright (c) 2026 nayrid 3 + Copyright (c) {{COPYRIGHT_YEAR}} {{AUTHOR}} 4 4 5 5 Permission is hereby granted, free of charge, to any person obtaining a copy 6 6 of this software and associated documentation files (the "Software"), to deal
+2 -2
license_header.txt
··· 1 - This file is part of <project name>, licensed under the MIT License. 1 + This file is part of {{PROJECT_NAME}}, licensed under the MIT License. 2 2 3 - Copyright (c) 2026 nayrid 3 + Copyright (c) {{COPYRIGHT_YEAR}} {{AUTHOR}} 4 4 5 5 Permission is hereby granted, free of charge, to any person obtaining a copy 6 6 of this software and associated documentation files (the "Software"), to deal
+1 -1
readme.md
··· 11 11 Run the rename script to set your group, project name, and Java package: 12 12 13 13 ```bash 14 - ./.scripts/rename-template.sh -g "com.acme" -p "my-project" 14 + ./.scripts/rename-template.sh -g "com.acme" -p "my-project" -n "acme-snapshots" -s "https://repo.acme.com/snapshots" -N "acme-releases" -r "https://repo.acme.com/releases" -a "Acme, Inc." -y "2026" 15 15 ``` 16 16 17 17 ## Checkstyle
+7 -4
settings.gradle.kts
··· 1 + enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 + 1 3 dependencyResolutionManagement { 2 - @Suppress("UnstableApiUsage") 3 4 repositories { 4 5 mavenCentral() 5 6 } ··· 12 13 includeBuild("build-logic") 13 14 } 14 15 15 - rootProject.name = "template" 16 + rootProject.name = "{{PROJECT_NAME}}" 16 17 17 - include("api") 18 - project(":api").projectDir = file("api") 18 + sequenceOf("api").forEach { 19 + include("${rootProject.name}-$it") 20 + project(":${rootProject.name}-$it").projectDir = file("${rootProject.name}-$it") 21 + }
+39
template-api/src/test/java/com/example/template/TemplateTest.java
··· 1 + /* 2 + * This file is part of <project name>, licensed under the MIT License. 3 + * 4 + * Copyright (c) 2026 nayrid 5 + * 6 + * Permission is hereby granted, free of charge, to any person obtaining a copy 7 + * of this software and associated documentation files (the "Software"), to deal 8 + * in the Software without restriction, including without limitation the rights 9 + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 + * copies of the Software, and to permit persons to whom the Software is 11 + * furnished to do so, subject to the following conditions: 12 + * 13 + * The above copyright notice and this permission notice shall be included in all 14 + * copies or substantial portions of the Software. 15 + * 16 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 + * SOFTWARE. 23 + */ 24 + package {{PACKAGE_NAME}}; 25 + 26 + import static org.junit.jupiter.api.Assertions.assertTrue; 27 + 28 + import org.jspecify.annotations.NullMarked; 29 + import org.junit.jupiter.api.Test; 30 + 31 + @NullMarked 32 + class TemplateTest { 33 + 34 + @Test 35 + void testTemplate() { 36 + assertTrue(true); 37 + } 38 + 39 + }