From d10f3dcf0d4e3e147e0248f72c018c20a26818c5 Mon Sep 17 00:00:00 2001 From: h1romas4 Date: Wed, 4 Oct 2023 15:04:36 +0900 Subject: [PATCH] [build] add syncImage Gradle task --- build.gradle | 31 ++++- buildSrc/build.gradle | 13 ++ buildSrc/src/main/groovy/AsciidocTasks.groovy | 117 ++++++++++++++++++ 3 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 buildSrc/build.gradle create mode 100644 buildSrc/src/main/groovy/AsciidocTasks.groovy diff --git a/build.gradle b/build.gradle index 41332f6..f9cf454 100644 --- a/build.gradle +++ b/build.gradle @@ -69,21 +69,46 @@ asciidoctorj { attributes 'source-highlighter': 'rouge' } +/** + * docs/ 配下のファイルを全て削除するタスク + * docs タスクの依存で直接は実行しない。 + */ task cleanDocs(type: Delete) { - // Delete all files in the docs directory once delete fileTree('docs/') { include '**/*' } } +/** + * Asciidoc 文章とファイルシステムの画像の整合性を確認するユーティリティタスク + * 未使用画像とリンク切れ画像の一覧を出力する。 + * + * ./gradlew syncImage + * + * @see buildSrc/src/main/groovy/AsciidocTasks.groovy + */ +tasks.register('syncImage', SyncImageTask) { + // 起点の文書ディレクトリ + baseDir = file('src/docs/asciidoc') + // 起点の Asciidoc 文書(include を辿る) + index = 'index.adoc' + // ファイルシステム上で画像として認識する拡張子 + imageExt = ['png', 'jpg', 'jpeg', 'svg'] +} + +/** + * Asciidoc 文書から HTML/PDF 文書を生成し docs 配下に出力するタスク + * + * ./gradlew docs + */ task docs(dependsOn: [asciidoctor, asciidoctorPdf, cleanDocs]) doLast { - // Copy documents created by asciidoctor to the docs directory + // build に生成された文書を docs にコピー copy { from 'build/docs/asciidoc/index.html' from 'build/docs/asciidocPdf/index.pdf' into 'docs/' } - // Copy images to docs directory for index.html + // build に出力されたダイアログ生成画像を含む全ての画像を HTML 文書用に docs にコピー copy { from 'build/docs/asciidoc/' include 'Chapter*/images/*' diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 0000000..a73ee2a --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,13 @@ +plugins { + id 'groovy' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation gradleApi() + implementation localGroovy() + implementation 'org.asciidoctor:asciidoctorj:2.5.6' +} diff --git a/buildSrc/src/main/groovy/AsciidocTasks.groovy b/buildSrc/src/main/groovy/AsciidocTasks.groovy new file mode 100644 index 0000000..7be3ad2 --- /dev/null +++ b/buildSrc/src/main/groovy/AsciidocTasks.groovy @@ -0,0 +1,117 @@ +import java.lang.Comparable.PathSensitivity.* + +import groovy.io.FileType + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.Input + +import org.asciidoctor.Asciidoctor +import org.asciidoctor.SafeMode +import org.asciidoctor.Options + +/** + * Asciidoc 文章とファイルシステムの画像の整合性を確認する Gradle タスク + * + * - 未使用画像の抽出 + * - リンク切れ画像の抽出 + */ +abstract class SyncImageTask extends DefaultTask { + /** + * 処理起点ディレクトリ + */ + @InputDirectory + File baseDir + + /** + * 起点 Asciidoc 文書(include も処理する) + */ + @Input + def index + + /** + * 画像として認識するファイルシステム上の拡張子 + */ + @Input + def imageExt = ['png', 'jpg'] + + /** + * 未使用画像をファイルシステムから削除するかどうかを決定するフラグ(TODO: 未実装) + */ + @Input + boolean remove = false + + /** + * Asciidoc 文書から image ブロックの画像パスをリスト取得 + * + * @param File baseDir Asciidoc 文書が格納されているディレクトリ + * @param String index 起点となる Asciidoc 文書(この文書から include を辿る) + * @return [] baseDir からの相対画像パスリスト + */ + static def searchImageInAdoc(baseDir, index) { + def asciidoctor = Asciidoctor.Factory.create(); + def options = Options.builder().safe(SafeMode.SAFE).baseDir(baseDir).build() + def document = asciidoctor.load(new File("${baseDir}/${index}").getText(), options); + + document.findBy(["context": ":image"]).collect { + "${it.attributes.imagesdir}/${it.attributes.target}" + } + } + + /** + * ディレクトリから指定された拡張子のファイルをリストで返却 + * @param File baseDir 検索ベースディレクトリ + * @param extension 検索拡張子文字列リスト + * @return [] baseDir からの相対パスリスト + */ + static def searchExtInDir(File baseDir, extension) { + def list = [] + def basePath = "${baseDir}/" + def ext = extension.collect { + ".${it}" + } + baseDir.eachFileRecurse(FileType.FILES) {file -> + ext.each { + if(file.name.endsWith(it)) { + list << (file.path - basePath) + } + } + } + list + } + + /** + * リスト形式の文字列を表示用に縦に出力 + * @param lists 文字列リスト + * @param prefix 前置文字列 + */ + static def print(lists, prefix = "") { + lists.each { + println "${prefix}${it}" + } + } + + /** + * Gradle タスクエントリーポイント + * + * @return + */ + @TaskAction + def sync() { + def imagesInAdoc = searchImageInAdoc(baseDir, index).sort() + def imagesInDir = searchExtInDir(baseDir, imageExt).sort() + + def unused = imagesInDir - imagesInAdoc + def notFound = imagesInAdoc - imagesInDir + + if(unused.size() != 0) { + println "@Unused images:" + print unused + } + if(notFound.size() != 0) { + println "@Image not found:" + print notFound + } + } +}