diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..5e8201ff --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# 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: "maven" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 27e606de..681537f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,78 +1,44 @@ -name: Build and package +name: Build and Deploy Snapshots on: push: branches: - master - tags: - - 'v*' - workflow_dispatch: - inputs: - version: - description: 'Version to create release for if not empty' - required: false - default: '' - jobs: build: runs-on: ubuntu-latest - + permissions: + contents: write + packages: write steps: - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v4 + uses: actions/setup-java@v3 with: java-version: '11' distribution: 'adopt' cache: maven -# To handle both tag and manual runs, run a script that will define all required vars - - name: Define version to build - run: ./.github/define_release_version.sh ${{ github.event.inputs.version }} - -# Building application - - name: Build with Maven - run: mvn --batch-mode verify -DversionSuffix=$RELEASE_VERSION_SUFFIX - - run: mkdir dist && cp cli/target/*.tar.gz dist - - uses: actions/upload-artifact@v4 - if: env.RELEASE_VERSION == '' - with: - name: cli.tar.gz - path: dist - retention-days: 20 - -# Pushing to docker registry - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_HUB_USER }} - password: ${{ secrets.DOCKER_HUB_TOKEN }} - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: ./cli - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ secrets.DOCKER_HUB_USER }}/text2confl:${{env.RELEASE_DOCKER_TAG}} - build-args: | - VERSION=${{env.RELEASE_VERSION_SUFFIX}} - cache-from: type=registry,ref=${{ secrets.DOCKER_HUB_USER }}/text2confl:buildcache - cache-to: type=registry,ref=${{ secrets.DOCKER_HUB_USER }}/text2confl:buildcache,mode=max - -# If there is a tag trigger, creating release - - name: Compute changelog - run: ./.github/create_release_changes.sh - if: startsWith(github.ref, 'refs/tags/') - - name: Create github release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + - name: Deploy to Github packages + run: mvn --batch-mode clean deploy -Pgithub,coverage + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + - name: Set up Java for publishing to Maven Central Repository + uses: actions/setup-java@v3 with: - name: ${{env.RELEASE_VERSION}} - body_path: target/ci/CHANGELOG.md - token: ${{ secrets.RELEASE_TOKEN }} - files: | - dist/*.tar.gz + java-version: '11' + distribution: 'temurin' + cache: maven + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + - name: Publish to the Maven Central Repository + run: mvn --batch-mode clean deploy -Possrh env: - GITHUB_REPOSITORY: zeldigas/text2confl \ No newline at end of file + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c79fd0c7..f4d036a9 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -18,21 +18,8 @@ jobs: distribution: 'adopt' cache: maven - name: Build with Maven - run: mvn --batch-mode verify - - run: mkdir dist && cp cli/target/*.tar.gz dist - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Build docker image - uses: docker/build-push-action@v5 - with: - context: ./cli - platforms: linux/amd64,linux/arm64 - push: false - cache-from: type=registry,ref=${{ secrets.DOCKER_HUB_USER }}/text2confl:buildcache - - uses: actions/upload-artifact@v4 - with: - name: cli.tar.gz - path: dist - retention-days: 7 \ No newline at end of file + run: mvn --batch-mode verify -Pcoverage + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..883c2879 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,67 @@ +name: Release New Version +on: + workflow_dispatch: + inputs: + releaseversion: + description: 'Release version' + required: true +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'adopt' + cache: maven + - run: | + echo "Release version ${{ github.event.inputs.releaseversion }}!" + - name: Set projects Maven version to GitHub Action GUI set version + run: mvn versions:set "-DnewVersion=${{ github.event.inputs.releaseversion }}" + - name: Deploy to Github packages + run: mvn --batch-mode clean deploy -Pgithub + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Deploy to Maven Central Repository + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + cache: maven + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + - name: Set projects Maven version to GitHub Action GUI set version + run: mvn versions:set "-DnewVersion=${{ github.event.inputs.releaseversion }}" + - name: Publish to the Maven Central Repository + run: mvn --batch-mode clean deploy -Possrh + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + - name: Generate changelog + id: changelog + uses: metcalfc/changelog-generator@v4.1.0 + with: + myToken: ${{ secrets.GITHUB_TOKEN }} + - name: Create GitHub Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.event.inputs.releaseversion }} + release_name: ${{ github.event.inputs.releaseversion }} + body: | + ### Things that changed in this release + ${{ steps.changelog.outputs.changelog }} + draft: false + prerelease: false + diff --git a/README.md b/README.md index 5616ad55..b577af47 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ +## Disclaimer + +This is a fork of original [Text2Confl](https://github.com/zeldigas/text2confl) + +It's main purposes is to deploy artifacts to maven central. + # text2confl -![](https://img.shields.io/docker/v/zeldigas/text2confl?label=latest%20version&sort=semver) ![](https://img.shields.io/docker/image-size/zeldigas/text2confl?label=docker%20image%20size&sort=semver) +![](https://img.shields.io/docker/v/zeldigas/text2confl?label=docker%20version&sort=semver) ![](https://img.shields.io/docker/image-size/zeldigas/text2confl?label=docker%20image%20size&sort=semver) + +[![maven-central](https://img.shields.io/maven-central/v/io.github.text2confl/text2confl-cli.svg)](https://search.maven.org/artifact/io.github.text2confl/text2confl-cli) ![codecov](https://codecov.io/gh/text2confl/text2confl/branch/master/graph/badge.svg) Is a tool for publishing documentation written in structured text formats like markdown to Confluence (either server or cloud edition). @@ -14,6 +22,8 @@ find something missing - feel free to create an issue and describe your needs. ## User guide +### CLI + To get started and learn how to start using text2confl, consult with [user guide](docs/user-guide.md). If you want to quickly check how `text2confl` works you can upload documentation of text2confl itself. For this you need @@ -37,6 +47,18 @@ Will upload docs producing pages similar to this one: allow making space or pages available to non-members of wiki (wide open to internet) that's why so far you need to publish to your own server or spend 10-15 minutes and create your own free wiki in Confluence Cloud. +### Maven + +```xml + + io.github.text2confl + text2confl-core + 0.16.2 + +``` + +Then + ## Design and usability goals Here are a number of key principles that tool tries to follow: diff --git a/cli/pom.xml b/cli/pom.xml index 35f5928a..b47f30ff 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -1,24 +1,31 @@ - + - com.github.zeldigas.confluence - parent - 1.0.0-SNAPSHOT + io.github.text2confl + text2confl-parent + 0.16.2-SNAPSHOT 4.0.0 - cli - - - - - + text2confl-cli + Text2Confl Command Line Interface - text2confl${versionSuffix} + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + true + lib + com.github.zeldigas.text2confl.cli.MainKt + + + + org.codehaus.mojo flatten-maven-plugin @@ -50,6 +57,7 @@ maven-assembly-plugin 3.6.0 + false src/assembly/cli-package.xml @@ -83,8 +91,8 @@ logback-classic - com.github.zeldigas.confluence - core + io.github.text2confl + text2confl-core ${project.version} diff --git a/cli/src/assembly/cli-package.xml b/cli/src/assembly/cli-package.xml index a18e3a8f..8c70f593 100644 --- a/cli/src/assembly/cli-package.xml +++ b/cli/src/assembly/cli-package.xml @@ -7,32 +7,29 @@ tar.gz dir - - - - com.github.zeldigas.* - - lib - - - - com.github.zeldigas.* - - ${artifact.artifactId}.${artifact.extension} - app - - - src/assembly/bin + ${project.basedir}/src/assembly/bin 755 - + / + + ${project.build.directory}/${project.artifactId}-${project.version}.jar + / + ../LICENSE + + + lib + + ${project.groupId}:${project.artifactId}:jar:* + + + \ No newline at end of file diff --git a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/CliOptions.kt b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/CliOptions.kt index 7bc587d9..f710643c 100644 --- a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/CliOptions.kt +++ b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/CliOptions.kt @@ -58,8 +58,18 @@ fun ParameterHolder.httpLoggingLevel() = option( fun ParameterHolder.httpRequestTimeout() = option( "--http-request-timeout", - help = "Http request timeout in milliseconds. Default " -).long() + help = "Http request timeout in milliseconds. Default 30 000 " +).long().default(30000) + +fun ParameterHolder.httpSocketTimeout() = option( + "--http-socket-timeout", + help = "Http socket timeout in milliseconds. Default 30 000 " +).long().default(30000) + +fun ParameterHolder.httpConnectTimeout() = option( + "--http-connect-timeout", + help = "Http connect timeout in milliseconds. Default 30 000 " +).long().default(30000) internal interface WithConfluenceServerOptions { val confluenceUrl: Url? @@ -69,6 +79,8 @@ internal interface WithConfluenceServerOptions { val skipSsl: Boolean? val httpLogLevel: LogLevel val httpRequestTimeout: Long? + val httpConnectTimeout: Long? + val httpSocketTimeout: Long? val confluenceAuth: ConfluenceAuth get() = when { @@ -93,7 +105,9 @@ internal interface WithConfluenceServerOptions { skipSsl = skipSsl ?: defaultSslSkip, auth = confluenceAuth, httpLogLevel = httpLogLevel, - requestTimeout = httpRequestTimeout + requestTimeout = httpRequestTimeout, + connectTimeout = httpConnectTimeout, + socketTimeout = httpSocketTimeout ) fun askForSecret(prompt: String, requireConfirmation: Boolean = true): String? diff --git a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/DumpToMarkdown.kt b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/DumpToMarkdown.kt index 8923ce3f..bc0234e5 100644 --- a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/DumpToMarkdown.kt +++ b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/DumpToMarkdown.kt @@ -22,6 +22,8 @@ class DumpToMarkdown : CliktCommand(name = "export-to-md", help = "Exports confl override val skipSsl: Boolean? by skipSsl() override val httpLogLevel: LogLevel by httpLoggingLevel() override val httpRequestTimeout: Long? by httpRequestTimeout() + override val httpSocketTimeout: Long? by httpSocketTimeout() + override val httpConnectTimeout: Long? by httpConnectTimeout() val space: String? by confluenceSpace() private val pageId: String? by option("--page-id", help = "Id of page that you want to dump") diff --git a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/PrintingUploadOperationsTracker.kt b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/PrintingUploadOperationsTracker.kt index c65c030c..8f03f605 100644 --- a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/PrintingUploadOperationsTracker.kt +++ b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/PrintingUploadOperationsTracker.kt @@ -45,6 +45,10 @@ class PrintingUploadOperationsTracker( describeModifiedPage("Updated labels/attachments:", pageResult.serverPage, pageResult.local, labelUpdate, attachmentsUpdated) } } + + is PageOperationResult.Failed -> { + printWithPrefix("${red("Failed:")} ${failedPage(pageResult)}") + } } } @@ -159,4 +163,17 @@ class PrintingUploadOperationsTracker( private fun printWithPrefix(msg: String) { printer("$prefix$msg") } + + private fun failedPage(error: PageOperationResult.Failed): String { + return buildString { + append("\"") + append(red(error.local.title)) + append("\"") + append(" (") + append(error.status) + append(")") + append(" ") + append(error.body) + } + } } \ No newline at end of file diff --git a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/Upload.kt b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/Upload.kt index 5966f83e..4f2e2e52 100644 --- a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/Upload.kt +++ b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/Upload.kt @@ -33,6 +33,8 @@ class Upload : CliktCommand(name = "upload", help = "Converts source files and u override val skipSsl: Boolean? by skipSsl() override val httpLogLevel: LogLevel by httpLoggingLevel() override val httpRequestTimeout: Long? by httpRequestTimeout() + override val httpSocketTimeout: Long? by httpSocketTimeout() + override val httpConnectTimeout: Long? by httpConnectTimeout() override val spaceKey: String? by confluenceSpace() private val parentId: String? by option("--parent-id", help = "Id of parent page where root pages should be added") @@ -87,6 +89,7 @@ class Upload : CliktCommand(name = "upload", help = "Converts source files and u } else { converter.convertDir(docs.toPath()) } + serviceProvider.createContentValidator().validate(result) val confluenceClient = serviceProvider.createConfluenceClient(clientConfig, dryRun) val publishUnder = resolveParent(confluenceClient, uploadConfig, directoryStoredParams) diff --git a/cli/src/test/kotlin/com/github/zeldigas/text2confl/cli/UploadTest.kt b/cli/src/test/kotlin/com/github/zeldigas/text2confl/cli/UploadTest.kt index dc15bedc..00bfcd67 100644 --- a/cli/src/test/kotlin/com/github/zeldigas/text2confl/cli/UploadTest.kt +++ b/cli/src/test/kotlin/com/github/zeldigas/text2confl/cli/UploadTest.kt @@ -110,7 +110,7 @@ internal class UploadTest( internal fun `Data from yaml config file`(@TempDir tempDir: Path) { val directoryConfig = sampleConfig().copy(tenant = "test1") directoryConfig.docsDir = tempDir - writeToFile(tempDir.resolve(".text2confl.yml"), directoryConfig) + writeToFile(tempDir.resolve("text2confl.yml"), directoryConfig) val result = mockk>() every { converter.convertDir(tempDir) } returns result diff --git a/confluence-client/pom.xml b/confluence-client/pom.xml index 437a3cf5..3072d1e6 100644 --- a/confluence-client/pom.xml +++ b/confluence-client/pom.xml @@ -1,15 +1,14 @@ - + - parent - com.github.zeldigas.confluence - 1.0.0-SNAPSHOT + io.github.text2confl + text2confl-parent + 0.16.2-SNAPSHOT 4.0.0 - - confluence-client + + text2confl-confluence-client + Text2Confl Confluence Client Implementation of http client for confluence diff --git a/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClient.kt b/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClient.kt index f67f4eae..65ec7874 100644 --- a/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClient.kt +++ b/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClient.kt @@ -80,6 +80,9 @@ interface ConfluenceClient { class PageNotCreatedException(val title: String, val status: Int, val body: String?) : RuntimeException("Failed to create '$title' page: status=$status, body:\n$body") +class PageNotUpdatedException(val id: String, val status: Int, val body: String?) : + RuntimeException("Failed to update '$id' page: status=$status, body:\n$body") + class PageNotFoundException : RuntimeException() class TooManyPagesFound(val pages: List) : RuntimeException() diff --git a/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClientConfig.kt b/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClientConfig.kt index 66c50d02..c1736bc3 100644 --- a/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClientConfig.kt +++ b/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClientConfig.kt @@ -11,5 +11,7 @@ data class ConfluenceClientConfig( val skipSsl: Boolean, val auth: ConfluenceAuth, val httpLogLevel: LogLevel = LogLevel.NONE, - val requestTimeout: Long? = null, + val requestTimeout: Long? = 30000, + val connectTimeout: Long? = 30000, + val socketTimeout: Long? = 30000 ) \ No newline at end of file diff --git a/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClientImpl.kt b/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClientImpl.kt index 6cb2944a..d7046b7b 100644 --- a/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClientImpl.kt +++ b/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClientImpl.kt @@ -8,6 +8,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.engine.cio.* +import io.ktor.client.network.sockets.* import io.ktor.client.plugins.* import io.ktor.client.plugins.auth.* import io.ktor.client.plugins.contentnegotiation.* @@ -132,6 +133,10 @@ class ConfluenceClientImpl( response.body() } catch (e: ContentConvertException) { throw PageNotCreatedException(value.title, response.status.value, response.bodyAsText()) + } catch (e: ConnectTimeoutException) { + throw PageNotCreatedException(value.title, response.status.value, response.bodyAsText()) + } catch (e: HttpRequestTimeoutException) { + throw PageNotCreatedException(value.title, response.status.value, response.bodyAsText()) } } @@ -179,9 +184,13 @@ class ConfluenceClientImpl( setBody(body) } if (response.status.isSuccess()) { - return response.readApiResponse() + try { + return response.readApiResponse() + } catch (e: ConnectTimeoutException) { + throw PageNotUpdatedException(pageId, response.status.value, response.bodyAsText()) + } } else { - throw RuntimeException("Failed to update $pageId: ${response.bodyAsText()}") + throw PageNotUpdatedException(pageId, response.status.value, response.bodyAsText()) } } @@ -340,7 +349,7 @@ private suspend inline fun HttpResponse.readApiResponse(expectSucces parseAndThrowConfluencError() } val contentType = contentType() - if (contentType != null && ContentType.Application.Json.match(contentType)){ + if (contentType != null && ContentType.Application.Json.match(contentType)) { try { return body() } catch (e: JsonConvertException) { @@ -357,7 +366,7 @@ private suspend fun HttpResponse.parseAndThrowConfluencError(): Nothing { } private data class PageSearchResult( - val results: List, + val results: List = emptyList(), val start: Int, val limit: Int, val size: Int @@ -367,10 +376,12 @@ fun confluenceClient( config: ConfluenceClientConfig ): ConfluenceClient { val client = HttpClient(CIO) { + install(HttpTimeout) { + requestTimeoutMillis = config.requestTimeout + connectTimeoutMillis = config.connectTimeout + socketTimeoutMillis = config.socketTimeout + } engine { - if (config.requestTimeout != null) { - requestTimeout = config.requestTimeout - } if (config.skipSsl) { https { trustManager = object : X509TrustManager { diff --git a/convert/pom.xml b/convert/pom.xml index 2795eadd..85a501c6 100644 --- a/convert/pom.xml +++ b/convert/pom.xml @@ -3,16 +3,18 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - parent - com.github.zeldigas.confluence - 1.0.0-SNAPSHOT + io.github.text2confl + text2confl-parent + 0.16.2-SNAPSHOT 4.0.0 - convert + text2confl-convert + Text2Confl Convertor + Conversion to Confluence Storage Format - 0.64.4 + 0.64.8 2.5.11 2.2.17 @@ -99,6 +101,11 @@ ${asciidoctorj-diagram.version} runtime + + org.jsoup + jsoup + 1.17.2 + @@ -142,4 +149,38 @@ + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.6.1 + + + unpack + generate-resources + + unpack + + + + + org.sahli.asciidoc.confluence.publisher + asciidoc-confluence-publisher-converter + 0.22.0 + jar + true + ${project.build.directory}/confluence-publisher-templates + **/*.slim,**/*.rb + + + + + + + + \ No newline at end of file diff --git a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/AttachmentCollector.kt b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/AttachmentCollector.kt index ecf8789b..a4e51de1 100644 --- a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/AttachmentCollector.kt +++ b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/AttachmentCollector.kt @@ -6,6 +6,7 @@ import java.net.URI import java.nio.file.Path import java.nio.file.Paths import kotlin.io.path.exists +import kotlin.io.path.isRegularFile class AttachmentCollector( private val referencesProvider: ReferenceProvider, @@ -62,7 +63,7 @@ class AttachmentCollector( private fun lookupInDirAndAdd(dir: Path, pathToFile: String, effectiveName: String): Attachment? { val file = dir.resolve(pathToFile).normalize() - return if (file.exists()) { + return if (file.exists() && file.isRegularFile()) { logger.debug { "File exists, adding as attachment: $file with ref $effectiveName" } val attachment = Attachment.fromLink(effectiveName, file) attachmentsRegistry.register(effectiveName, attachment) diff --git a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/Converter.kt b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/Converter.kt index 89e7757b..b6d92ecb 100644 --- a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/Converter.kt +++ b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/Converter.kt @@ -6,6 +6,7 @@ import com.github.zeldigas.text2confl.convert.confluence.LanguageMapper import com.github.zeldigas.text2confl.convert.confluence.ReferenceProvider import com.github.zeldigas.text2confl.convert.markdown.MarkdownConfiguration import com.github.zeldigas.text2confl.convert.markdown.MarkdownFileConverter +import io.github.oshai.kotlinlogging.KotlinLogging import java.io.File import java.nio.file.Path import kotlin.io.path.exists @@ -62,6 +63,9 @@ internal class UniversalConverter( val converters: Map, val pagesDetector: PagesDetector, ) : Converter { + companion object { + private val logger = KotlinLogging.logger {} + } override fun convertFile(file: Path): Page { val converter = converterFor(file) @@ -79,8 +83,8 @@ internal class UniversalConverter( override fun convertDir(dir: Path): List { val documents = scanDocuments(dir) + logger.info { "Found " + documents.size + " documents in " + dir } checkForDuplicates(dir, documents) - return convertFilesInDirectory( dir, ConvertingContext(ReferenceProvider.fromDocuments(dir, documents), conversionParameters, space) diff --git a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/PageContent.kt b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/PageContent.kt index 18f0ea94..939a7238 100644 --- a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/PageContent.kt +++ b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/PageContent.kt @@ -1,5 +1,8 @@ package com.github.zeldigas.text2confl.convert +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jsoup.Jsoup +import org.jsoup.parser.Parser import java.io.ByteArrayInputStream import java.nio.file.Path import java.security.DigestInputStream @@ -89,9 +92,13 @@ data class Attachment( data class PageContent( val header: PageHeader, - val body: String, + var body: String, val attachments: List ) { + companion object { + private val logger = KotlinLogging.logger { } + } + val hash by lazy { val bytes = body.toByteArray() val md = MessageDigest.getInstance("SHA-256") @@ -99,9 +106,14 @@ data class PageContent( toBase64(digest) } + fun fixHtml(): PageContent { + val document = Jsoup.parse(body, Parser.xmlParser()) + body = document.html() + return this + } + fun validate(): Validation { val stack: Deque = LinkedList() - try { for (event in traverseDocument(body)) { when { diff --git a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/asciidoc/AsciidocParser.kt b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/asciidoc/AsciidocParser.kt index 03e02b76..13388cc4 100644 --- a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/asciidoc/AsciidocParser.kt +++ b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/asciidoc/AsciidocParser.kt @@ -6,6 +6,7 @@ import org.asciidoctor.* import org.asciidoctor.ast.Document import java.nio.file.Path import java.nio.file.Paths +import kotlin.io.path.Path import kotlin.io.path.div @@ -14,7 +15,7 @@ class AsciidocParser( ) { companion object { - private val TEMPLATES_LOCATION = "/com/github/zeldigas/text2confl/asciidoc" + private val TEMPLATES_LOCATION = "com/github/zeldigas/text2confl/asciidoc" } private val ADOC: Asciidoctor by lazy { @@ -27,9 +28,10 @@ class AsciidocParser( } private val templatesLocation: Path by lazy { - val templateResources = AsciidocParser::class.java.getResource(TEMPLATES_LOCATION)!!.toURI() + val templateResources = AsciidocParser::class.java.classLoader.getResource(TEMPLATES_LOCATION)!!.toURI() if (templateResources.scheme == "file") { - Paths.get(templateResources) + val mainPath: String = Paths.get(templateResources).toString() + Path(mainPath) } else { val dest = config.workdir / "templates" diff --git a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/confluence/ReferenceProvider.kt b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/confluence/ReferenceProvider.kt index 9fbbe5ff..964d88e9 100644 --- a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/confluence/ReferenceProvider.kt +++ b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/confluence/ReferenceProvider.kt @@ -1,9 +1,11 @@ package com.github.zeldigas.text2confl.convert.confluence import com.github.zeldigas.text2confl.convert.PageHeader +import java.nio.file.InvalidPathException import io.github.oshai.kotlinlogging.KotlinLogging import java.net.URLDecoder import java.nio.file.Path +import java.util.regex.Pattern import kotlin.io.path.relativeTo interface ReferenceProvider { @@ -41,18 +43,27 @@ class ReferenceProviderImpl(private val basePath: Path, documents: Map path.relativeTo(basePath).normalize() to header }.toMap() override fun resolveReference(source: Path, refTo: String): Reference? { - if (URI_DETECTOR.find(refTo) != null) { - log.debug { "$refTo detected as link in $source" } - return null - } + + if (refTo.startsWith(MAILTO_DETECTOR)) return null + if (refTo.startsWith(LOCALHOST_DETECTOR)) return null + if (isValid(refTo)) return null + val normalizedRef = URLDecoder.decode(refTo, "UTF-8") if (normalizedRef.startsWith("#")) return Anchor(normalizedRef.substring(1)) @@ -60,10 +71,17 @@ class ReferenceProviderImpl(private val basePath: Path, documents: Map @@ -61,14 +64,20 @@ class KrokiDiagramsGenerator( override fun generate(source: String, target: Path, attributes: Map): ImageInfo { val request = createRequest(source, attributes) runBlocking { - val result = client.post(server.toURL()) { - contentType(ContentType.Application.Json) - setBody(request) - } - if (result.status != HttpStatusCode.OK) { - throw DiagramGenerationFailedException(result.body()) + try { + val result = client.post(server.toURL()) { + contentType(ContentType.Application.Json) + setBody(request) + } + if (result.status != HttpStatusCode.OK) { + throw DiagramGenerationFailedException(result.body()) + } + target.writeBytes(result.body()) + } catch (ex : ConnectException){ + logger.error { "Trying to connect to $server" } + throw DiagramGenerationFailedException(ex.message) } - target.writeBytes(result.body()) + } return ImageInfo() } diff --git a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/markdown/diagram/MermaidDiagramsGenerator.kt b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/markdown/diagram/MermaidDiagramsGenerator.kt index a2a808b7..393becd5 100644 --- a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/markdown/diagram/MermaidDiagramsGenerator.kt +++ b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/markdown/diagram/MermaidDiagramsGenerator.kt @@ -20,7 +20,7 @@ class MermaidDiagramsGenerator( private val SUPPORTED_FORMATS = setOf("png", "svg") private val PUPPETER_CONFIG_ENV = "T2C_PUPPEETER_CONFIG" - private val log = KotlinLogging.logger {} + private val logger = KotlinLogging.logger {} } constructor(config: MermaidDiagramsConfiguration, commandExecutor: CommandExecutor = OsCommandExecutor()) : this( @@ -67,11 +67,11 @@ class MermaidDiagramsGenerator( val result = try { commandExecutor.execute(cmd(command) { flag("-V") }) } catch (ex: Exception) { - log.debug(ex) { "Failed to execute command" } + logger.debug(ex) { "Failed to execute command" } return false } return if (result.status == 0) { - log.info { "Mermaid version: ${result.output}" } + logger.info { "Mermaid version: ${result.output}" } true } else { false diff --git a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/markdown/diagram/PlantUmlDiagramsGenerator.kt b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/markdown/diagram/PlantUmlDiagramsGenerator.kt index 4d9a548c..e73514c9 100644 --- a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/markdown/diagram/PlantUmlDiagramsGenerator.kt +++ b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/markdown/diagram/PlantUmlDiagramsGenerator.kt @@ -16,7 +16,7 @@ class PlantUmlDiagramsGenerator( val SUPPORTED_LANGS = setOf("plantuml", "puml") val SUPPORTED_FORMATS = setOf("svg", "png", "eps") const val DEFAULT_FORMAT = "png" - private val log = KotlinLogging.logger { } + private val logger = KotlinLogging.logger { } } constructor(config: PlantUmlDiagramsConfiguration, commandExecutor: CommandExecutor = OsCommandExecutor()) : this( @@ -59,11 +59,11 @@ class PlantUmlDiagramsGenerator( val result = try { commandExecutor.execute(cmd(command) { flag("-version") }) } catch (ex: Exception) { - log.debug(ex) { "Failed to execute command" } + logger.debug(ex) { "Failed to execute command" } return false } return if (result.status == 0) { - log.info { "PlantUml version: ${result.output.lines().firstOrNull()}" } + logger.info { "PlantUml version: ${result.output.lines().firstOrNull()}" } true } else { false diff --git a/convert/src/test/kotlin/com/github/zeldigas/text2confl/convert/PageContentTest.kt b/convert/src/test/kotlin/com/github/zeldigas/text2confl/convert/PageContentTest.kt index 0fa84fcc..7f48e8b5 100644 --- a/convert/src/test/kotlin/com/github/zeldigas/text2confl/convert/PageContentTest.kt +++ b/convert/src/test/kotlin/com/github/zeldigas/text2confl/convert/PageContentTest.kt @@ -11,6 +11,7 @@ import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource import java.nio.file.Path +import java.util.* import kotlin.io.path.Path import kotlin.io.path.writeBytes @@ -70,6 +71,7 @@ class PageContentTest { @Test internal fun `Invalid result for unbalanced xml`() { + Locale.setDefault(Locale.ENGLISH); val sampleXml = """

hello world

@@ -87,4 +89,27 @@ class PageContentTest { .transform { it.issue } .contains("[5:3] The element type \"p\" must be terminated by the matching end-tag \"

\". Start tag location - [3:4]") } + + @Test + internal fun `Valid result for unbalanced xml`() { + Locale.setDefault(Locale.ENGLISH); + val sampleXml = """ +
+

hello world

+

hello world </p> +

hello world

+
""".trimIndent() + print(sampleXml) + assertThat( + PageContent( + PageHeader("", emptyMap()), + sampleXml, + emptyList() + ) + .fixHtml() + .validate() + + ).isEqualTo(Validation.Ok) + } + } \ No newline at end of file diff --git a/convert/src/test/kotlin/com/github/zeldigas/text2confl/convert/confluence/ReferenceProviderImplTest.kt b/convert/src/test/kotlin/com/github/zeldigas/text2confl/convert/confluence/ReferenceProviderImplTest.kt index 1a12acd5..926e997b 100644 --- a/convert/src/test/kotlin/com/github/zeldigas/text2confl/convert/confluence/ReferenceProviderImplTest.kt +++ b/convert/src/test/kotlin/com/github/zeldigas/text2confl/convert/confluence/ReferenceProviderImplTest.kt @@ -88,4 +88,58 @@ internal class ReferenceProviderImplTest { assertThat(result).isNull() } + + + @Test + internal fun `Http resolution`() { + val result = providerImpl.resolveReference(Path("docs/one.md"), "http://github.com") + + assertThat(result).isNull() + } + + @Test + internal fun `Https resolution`() { + val result = providerImpl.resolveReference(Path("docs/one.md"), "https://github.com") + + assertThat(result).isNull() + } + + @Test + internal fun `Mailto resolution`() { + val result = providerImpl.resolveReference(Path("docs/one.md"), "mailto:john.doe@github.com") + + assertThat(result).isNull() + } + + + @Test + internal fun `French url resolution`() { + val result = providerImpl.resolveReference(Path("docs/one.md"), "http://github.com/handle'case") + + assertThat(result).isNull() + } + + @Test + internal fun `Parenthesis url resolution`() { + val result = providerImpl.resolveReference(Path("docs/one.md"), "http://github.com/handle()case") + + assertThat(result).isNull() + } + + + @Test + internal fun `Plus url resolution`() { + val result = providerImpl.resolveReference(Path("docs/one.md"), "https://github.com/handle/+/case") + + assertThat(result).isNull() + } + + + @Test + internal fun `localhost url resolution`() { + val result = providerImpl.resolveReference(Path("docs/one.md"), "localhost:9000") + + assertThat(result).isNull() + } + } \ No newline at end of file diff --git a/convert/src/test/kotlin/com/github/zeldigas/text2confl/convert/markdown/MarkdownAttachmentCollectorTest.kt b/convert/src/test/kotlin/com/github/zeldigas/text2confl/convert/markdown/MarkdownAttachmentCollectorTest.kt index e53e7844..d38bd5a8 100644 --- a/convert/src/test/kotlin/com/github/zeldigas/text2confl/convert/markdown/MarkdownAttachmentCollectorTest.kt +++ b/convert/src/test/kotlin/com/github/zeldigas/text2confl/convert/markdown/MarkdownAttachmentCollectorTest.kt @@ -145,4 +145,28 @@ internal class MarkdownAttachmentCollectorTest { assertThat(registry.collectedAttachments).isEmpty() } + + + @Test + internal fun `Attachment collection for folder`( + @TempDir dir: Path, + @MockK referenceProvider: ReferenceProvider + ) { + + Files.createDirectory(dir.resolve("folder")) + val ast = parser.parse( + """ + ["folder"](folder) + + """.trimIndent() + ) + + val doc = dir.resolve("doc.md") + + every { referenceProvider.resolveReference(doc, "folder") } returns Xref("test", null) + + MarkdownAttachmentCollector(doc, referenceProvider, registry).collectAttachments(ast) + + assertThat(registry.collectedAttachments).isEmpty() + } } \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index 401acf19..3de34e49 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -1,25 +1,24 @@ - + 4.0.0 - com.github.zeldigas.confluence - parent - 1.0.0-SNAPSHOT + io.github.text2confl + text2confl-parent + 0.16.2-SNAPSHOT - core + text2confl-core + Text2confl Core - com.github.zeldigas.confluence - convert + io.github.text2confl + text2confl-convert ${project.version} - com.github.zeldigas.confluence - confluence-client + io.github.text2confl + text2confl-confluence-client ${project.version} diff --git a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/ContentValidator.kt b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/ContentValidator.kt index 3d24ab7d..362cea40 100644 --- a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/ContentValidator.kt +++ b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/ContentValidator.kt @@ -2,14 +2,20 @@ package com.github.zeldigas.text2confl.core import com.github.zeldigas.text2confl.convert.Page import com.github.zeldigas.text2confl.convert.Validation +import io.github.oshai.kotlinlogging.KotlinLogging class ContentValidationFailedException(val errors: List) : RuntimeException() interface ContentValidator { fun validate(content: List) + fun fixHtml(content: List) } class ContentValidatorImpl : ContentValidator { + + companion object { + private val logger = KotlinLogging.logger { } + } override fun validate(content: List) { val foundIssues: MutableList = arrayListOf() collectErrors(content, foundIssues) @@ -18,10 +24,20 @@ class ContentValidatorImpl : ContentValidator { } } + override fun fixHtml(content: List) { + logger.info { "Fixing html pages : " + content.size } + for (page in content){ + page.content.fixHtml() + fixHtml(page.children) + } + } + private fun collectErrors(pages: List, foundIssues: MutableList) { for (page in pages) { + logger.debug { "Validating : ${page.source}: "} val validationResult = page.content.validate() if (validationResult is Validation.Invalid) { + logger.debug { validationResult.issue } foundIssues.add("${page.source}: ${validationResult.issue}") } collectErrors(page.children, foundIssues) diff --git a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/config/DirectoryConfig.kt b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/config/DirectoryConfig.kt index 225f0db6..878f9128 100644 --- a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/config/DirectoryConfig.kt +++ b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/config/DirectoryConfig.kt @@ -10,7 +10,7 @@ import kotlin.io.path.createTempDirectory import kotlin.io.path.div /** - * Holder of data that can be put to `.text2confl.yml` configuration file that is located in root directory of directory structure + * Holder of data that can be put to `text2confl.yml` configuration file that is located in root directory of directory structure */ data class DirectoryConfig( val server: String? = null, diff --git a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/config/IO.kt b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/config/IO.kt index 01e014b6..ca95bb1c 100644 --- a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/config/IO.kt +++ b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/config/IO.kt @@ -13,6 +13,7 @@ import kotlin.io.path.absolute import kotlin.io.path.exists import kotlin.io.path.isRegularFile +private val logger = KotlinLogging.logger { } private val mapper = JsonMapper.builder(YAMLFactory()) .addModule(kotlinModule()) @@ -21,9 +22,9 @@ private val mapper = JsonMapper.builder(YAMLFactory()) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build() -private val CONFIG_FILE_NAMES = listOf(".text2confl", "text2confl") -private val log = KotlinLogging.logger { } +private val CONFIG_FILE_NAME = "text2confl.yml" +private val CONFIG_FILE_NAMES = listOf(CONFIG_FILE_NAME) fun readDirectoryConfig(dirOfFile: Path): DirectoryConfig { val resolver: (String) -> Path = if (dirOfFile.isRegularFile()) { @@ -31,20 +32,28 @@ fun readDirectoryConfig(dirOfFile: Path): DirectoryConfig { } else { dirOfFile::resolve } - val docsDir = if (dirOfFile.isRegularFile()) dirOfFile.absolute().parent else dirOfFile - val configFile = CONFIG_FILE_NAMES.asSequence() - .flatMap { listOf("$it.yml", "$it.yaml") } + var directoryConfig = CONFIG_FILE_NAMES.asSequence() .map(resolver) .filter { it.exists() } + .map { mapper.readValue(it.toFile()) } .firstOrNull() - val directoryConfig:DirectoryConfig = if (configFile != null) { - log.debug { "Found config file $configFile" } - mapper.readValue(configFile.toFile()) - } else { - log.debug { "No config file found in $docsDir. Using defaults" } - DirectoryConfig() + + + if (directoryConfig == null) { + logger.debug { "No Conf File in $dirOfFile" } + logger.debug { "Try Loading conf from classpath" } + + val confFile = DirectoryConfig::class.java.classLoader.getResource(CONFIG_FILE_NAME) + if (confFile != null) { + logger.debug { "Found conf File : " + confFile.file } + directoryConfig = mapper.readValue(confFile) + } else { + logger.debug { "Create default Directory Config" } + directoryConfig = DirectoryConfig() + } } - directoryConfig.docsDir = docsDir + + directoryConfig.docsDir = if (dirOfFile.isRegularFile()) dirOfFile.absolute().parent else dirOfFile return directoryConfig } \ No newline at end of file diff --git a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/ContentUploader.kt b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/ContentUploader.kt index 638650b7..75ebed62 100644 --- a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/ContentUploader.kt +++ b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/ContentUploader.kt @@ -7,10 +7,8 @@ import com.github.zeldigas.text2confl.convert.Page import com.github.zeldigas.text2confl.convert.PageHeader import com.github.zeldigas.text2confl.core.config.Cleanup import io.github.oshai.kotlinlogging.KotlinLogging -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch +import io.ktor.client.network.sockets.* +import kotlinx.coroutines.* class ContentUploader( @@ -49,19 +47,67 @@ class ContentUploader( private val logger = KotlinLogging.logger {} } - suspend fun uploadPages(pages: List, space: String, parentPageId: String) { - val uploadedPages = uploadPagesRecursive(pages, space, parentPageId) + fun run(pages: List, space: String, parentPageId: String) = runBlocking { + withContext(Dispatchers.Default) { + uploadPages(pages, space, parentPageId) + } + } + + fun runBlocking(pages: List, space: String, parentPageId: String) = runBlocking { + withContext(Dispatchers.Default) { + uploadPagesBlocking(pages, space, parentPageId) + } + } + + suspend fun uploadPagesBlocking(pages: List, space: String, parentPageId: String) { + val uploadedPages = uploadPagesRecursiveBlocking(pages, space, parentPageId) + logger.info { "Uploaded Pages : " + uploadedPages.size } tracker.uploadsCompleted() + handleOrphans(uploadedPages) + } + + private suspend fun ContentUploader.handleOrphans(uploadedPages: List) { val uploadedPagesByParent = buildOrphanedRemovalRegistry(uploadedPages) deleteOrphans(uploadedPagesByParent) } + suspend fun uploadPages(pages: List, space: String, parentPageId: String) { + val uploadedPages = uploadPagesRecursive(pages, space, parentPageId) + tracker.uploadsCompleted() + handleOrphans(uploadedPages) + } + + private suspend fun uploadPagesRecursiveBlocking( + pages: List, + space: String, + parentPageId: String + ): List { + + val uploadedPages = ArrayList() + pages.forEach { + try { + val result = uploadPage(it, space, parentPageId) + uploadedPages.addAll( + buildList { + add(result) + if (it.children.isNotEmpty()) { + addAll(uploadPagesRecursiveBlocking(it.children, space, result.page.id)) + } + }) + } catch (e: ConnectTimeoutException) { + logger.error { e.message } + return emptyList() + } + } + return uploadedPages + } + private suspend fun uploadPagesRecursive( pages: List, space: String, parentPageId: String ): List { - return coroutineScope { + return supervisorScope { pages.map { page -> async { val result = uploadPage(page, space, parentPageId) @@ -85,7 +131,11 @@ class ContentUploader( val labelUpdate = pageUploadOperations.updatePageLabels(serverPage, page.content) val attachmentsUpdated = pageUploadOperations.updatePageAttachments(serverPage, page.content) tracker.pageUpdated(pageResult, labelUpdate, attachmentsUpdated) - logger.info { "Page uploaded: title=${page.title}, src=${page.source}: id=${serverPage.id}" } + if (pageResult is PageOperationResult.Failed){ + logger.warn { "Failed to upload Page : title=${page.title}, src=${page.source}" } + } else { + logger.info { "Page uploaded: title=${page.title}, src=${page.source}: id=${serverPage.id}" } + } PageUploadResult(parentId, serverPage, virtual = false) } else { logger.info { "Checking that virtual page exists and properly located: ${page.title}" } diff --git a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/DryRunClient.kt b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/DryRunClient.kt index b54651da..c9a328d4 100644 --- a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/DryRunClient.kt +++ b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/DryRunClient.kt @@ -8,7 +8,7 @@ import java.time.ZonedDateTime class DryRunClient(private val realClient: ConfluenceClient) : ConfluenceClient by realClient { companion object { - private val log = KotlinLogging.logger {} + private val logger = KotlinLogging.logger{} private const val UNDEFINED_ID = "(known after apply)" } @@ -17,7 +17,7 @@ class DryRunClient(private val realClient: ConfluenceClient) : ConfluenceClient updateParameters: PageUpdateOptions, expansions: List? ): ConfluencePage { - log.info { "(dryrun) Creating page under parent ${value.parentPage} with title ${value.title}" } + logger.info { "(dryrun) Creating page under parent ${value.parentPage} with title ${value.title}" } return ConfluencePage( UNDEFINED_ID, ContentType.page, @@ -36,7 +36,7 @@ class DryRunClient(private val realClient: ConfluenceClient) : ConfluenceClient value: PageContentInput, updateParameters: PageUpdateOptions ): ConfluencePage { - log.info { "(dryrun) Updating page $pageId with title ${value.title}" } + logger.info { "(dryrun) Updating page $pageId with title ${value.title}" } return ConfluencePage( pageId, ContentType.page, @@ -57,7 +57,7 @@ class DryRunClient(private val realClient: ConfluenceClient) : ConfluenceClient newParentId: String, updateParameters: PageUpdateOptions ): ConfluencePage { - log.info { "(dryrun) Changing parent of page $pageId with title ${title} to $newParentId" } + logger.info { "(dryrun) Changing parent of page $pageId with title ${title} to $newParentId" } return ConfluencePage( pageId, ContentType.page, @@ -76,7 +76,7 @@ class DryRunClient(private val realClient: ConfluenceClient) : ConfluenceClient newTitle: String, updateParameters: PageUpdateOptions ): ConfluencePage { - log.info { "(dryrun) Changing title of page with ${serverPage.id}: ${serverPage.title} -> $newTitle" } + logger.info { "(dryrun) Changing title of page with ${serverPage.id}: ${serverPage.title} -> $newTitle" } return serverPage.copy( title = newTitle, version = PageVersionInfo(serverPage.version!!.number + 1, true, ZonedDateTime.now()) @@ -84,7 +84,7 @@ class DryRunClient(private val realClient: ConfluenceClient) : ConfluenceClient } override suspend fun deletePage(pageId: String) { - log.info { "(dryrun) Deleting page $pageId" } + logger.info { "(dryrun) Deleting page $pageId" } } override suspend fun findChildPages(pageId: String, expansions: List?): List { @@ -96,15 +96,15 @@ class DryRunClient(private val realClient: ConfluenceClient) : ConfluenceClient } override suspend fun setPageProperty(pageId: String, name: String, value: PagePropertyInput) { - log.info { "(dryrun) Setting property on page $pageId: $name=${value.value}, version=${value.version.number}" } + logger.info { "(dryrun) Setting property on page $pageId: $name=${value.value}, version=${value.version.number}" } } override suspend fun deleteLabel(pageId: String, label: String) { - log.info { "(dryrun) Deleting label on page $pageId: $label" } + logger.info { "(dryrun) Deleting label on page $pageId: $label" } } override suspend fun addLabels(pageId: String, labels: List) { - log.info { "(dryrun) Adding labels on page $pageId: $labels" } + logger.info { "(dryrun) Adding labels on page $pageId: $labels" } } override suspend fun addAttachments( @@ -112,7 +112,7 @@ class DryRunClient(private val realClient: ConfluenceClient) : ConfluenceClient pageAttachmentInput: List ): PageAttachments { pageAttachmentInput.forEach { - log.info { "(dryrun) Creating attachment on page $pageId: ${contentDetails(it)}" } + logger.info { "(dryrun) Creating attachment on page $pageId: ${contentDetails(it)}" } } return PageAttachments(results = pageAttachmentInput.map { toServerAttachment(UNDEFINED_ID, it) }) } @@ -122,7 +122,7 @@ class DryRunClient(private val realClient: ConfluenceClient) : ConfluenceClient attachmentId: String, pageAttachmentInput: PageAttachmentInput ): Attachment { - log.info { "(dryrun) Updating attachment $attachmentId on page $pageId: ${contentDetails(pageAttachmentInput)}" } + logger.info { "(dryrun) Updating attachment $attachmentId on page $pageId: ${contentDetails(pageAttachmentInput)}" } return toServerAttachment(attachmentId, pageAttachmentInput) } @@ -135,6 +135,6 @@ class DryRunClient(private val realClient: ConfluenceClient) : ConfluenceClient "uploading ${pageAttachmentInput.content} with contentType=${pageAttachmentInput.contentType}, comment=${pageAttachmentInput.comment}" override suspend fun deleteAttachment(attachmentId: String) { - log.info { "(dryrun) Deleting attachment $attachmentId" } + logger.info { "(dryrun) Deleting attachment $attachmentId" } } } \ No newline at end of file diff --git a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/LoggingUploadOperationsTracker.kt b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/LoggingUploadOperationsTracker.kt new file mode 100644 index 00000000..2f11884f --- /dev/null +++ b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/LoggingUploadOperationsTracker.kt @@ -0,0 +1,202 @@ +package com.github.zeldigas.text2confl.core.upload + +import com.github.zeldigas.confclient.model.ConfluencePage +import com.github.zeldigas.text2confl.convert.Page +import io.github.oshai.kotlinlogging.KotlinLogging +import io.ktor.http.* +import java.util.concurrent.atomic.AtomicLong + +class LoggingUploadOperationsTracker( + val server: Url +) : UploadOperationTracker { + + companion object { + const val UILINK = "tinyui" + private val logger = KotlinLogging.logger { } + } + + private val updatedCount = AtomicLong(0L) + + private fun print(msg: String) { + logger.info { msg } + } + + override fun pageUpdated( + pageResult: PageOperationResult, + labelUpdate: LabelsUpdateResult, + attachmentsUpdated: AttachmentsUpdateResult + ) { + if (pageResult is PageOperationResult.NotModified + && labelUpdate == LabelsUpdateResult.NotChanged + && attachmentsUpdated == AttachmentsUpdateResult.NotChanged + ) return; + + updatedCount.incrementAndGet() + + when (pageResult) { + is PageOperationResult.Created -> { + print("Created: ${pageInfo(pageResult.serverPage, pageResult.local)}") + } + + is PageOperationResult.ContentModified-> { + describeModifiedPage( + "Updated:", + pageResult.serverPage, + pageResult.local, + labelUpdate, + attachmentsUpdated + ) + } + is PageOperationResult.LocationModified -> { + describeModifiedPage( + "Updated:", + pageResult.serverPage, + pageResult.local, + labelUpdate, + attachmentsUpdated + ) + } + + is PageOperationResult.NotModified -> { + if (labelUpdate != LabelsUpdateResult.NotChanged || attachmentsUpdated != AttachmentsUpdateResult.NotChanged) { + describeModifiedPage( + "Updated labels/attachments:", + pageResult.serverPage, + pageResult.local, + labelUpdate, + attachmentsUpdated + ) + } + } + is PageOperationResult.Failed -> { + logger.error { + "Failed: ${failedPage(pageResult)}" + } + } + } + } + + private fun describeModifiedPage( + operation: String, + serverPage: ServerPage, + local: Page, + labelUpdate: LabelsUpdateResult, + attachmentsUpdated: AttachmentsUpdateResult + ) { + val labelsAttachmentsInfo = labelsAttachmentsInfo(labelUpdate, attachmentsUpdated) + print( + "$operation ${ + pageInfo( + serverPage, + local + ) + }${if (labelsAttachmentsInfo.isNotBlank()) " $labelsAttachmentsInfo" else "" }" + ) + } + + private fun pageInfo(serverPage: ServerPage, page: Page): String = buildString { + append('"') + append(serverPage.title) + append('"') + append(" from - ") + append(page.source.normalize()) + append(".") + val uiLink = serverPage.links[UILINK] + if (uiLink != null) { + append(" URL - ") + append(URLBuilder(server).appendPathSegments(uiLink).buildString()) + append(".") + } + } + + private fun labelsAttachmentsInfo( + labelsUpdateResult: LabelsUpdateResult, + attachmentsUpdated: AttachmentsUpdateResult + ): String { + val labelsInfo = buildString { + if (labelsUpdateResult is LabelsUpdateResult.Updated) { + append("Labels ") + if (labelsUpdateResult.added.isNotEmpty()) { + append("+") + append("[") + append(labelsUpdateResult.added.joinToString(", ")) + append("]") + if (labelsUpdateResult.removed.isNotEmpty()) { + append(", ") + } + } + if (labelsUpdateResult.removed.isNotEmpty()) { + append("-") + append("[") + append(labelsUpdateResult.removed.joinToString(", ")) + append("]") + } + } + } + val attachmentsInfo = buildString { + if (attachmentsUpdated is AttachmentsUpdateResult.Updated) { + append("attachments: ") + append("added ${attachmentsUpdated.added.size}, ") + append(("modified ${attachmentsUpdated.modified.size}, ")) + append("removed ${attachmentsUpdated.removed.size}") + } + } + val labelsAttachmentsDetails = listOf(labelsInfo, attachmentsInfo).filter { it.isNotBlank() } + return if (labelsAttachmentsDetails.isEmpty()) { + return "" + } else { + labelsAttachmentsDetails.joinToString(", ", postfix = ".") + } + } + + override fun uploadsCompleted() { + val updated = updatedCount.get() + if (updated == 0L) { + print("All pages are up to date") + } + } + + override fun pagesDeleted(root: ConfluencePage, allDeletedPages: List) { + if (allDeletedPages.isEmpty()) return + + print(buildString { + append("Deleted: ") + append(deletedPage(allDeletedPages[0])) + if (allDeletedPages.size > 1) { + append(" with subpages:") + } + }) + + val tail = allDeletedPages.drop(1) + if (tail.isNotEmpty()) { + tail.forEach { page -> + print(" Deleted: ${deletedPage(page)}") + } + } + } + + private fun deletedPage(confluencePage: ConfluencePage): String { + return buildString { + append("\"") + append(confluencePage.title) + append("\"") + append(" (") + append(confluencePage.id) + append(")") + } + } + + private fun failedPage(error: PageOperationResult.Failed): String { + return buildString { + append("\"") + append(error.local.title) + append("\"") + append(" (") + append(error.status) + append(")") + append(" ") + append(error.body) + } + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/PageUploadOperations.kt b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/PageUploadOperations.kt index 56e0b343..26d5313e 100644 --- a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/PageUploadOperations.kt +++ b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/PageUploadOperations.kt @@ -26,6 +26,7 @@ interface PageUploadOperations { } sealed class PageOperationResult { + data class NotModified(override val local: Page, override val serverPage: ServerPage) : PageOperationResult() data class LocationModified( override val local: Page, @@ -41,6 +42,13 @@ sealed class PageOperationResult { val parentChanged: Boolean = false ) : PageOperationResult() + data class Failed( + override val local: Page, + override val serverPage: ServerPage, + val status: Int, + val body: String? + ): PageOperationResult() + abstract val local: Page abstract val serverPage: ServerPage } diff --git a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/PageUploadOperationsImpl.kt b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/PageUploadOperationsImpl.kt index ae2cda31..f04ae0d5 100644 --- a/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/PageUploadOperationsImpl.kt +++ b/core/src/main/kotlin/com/github/zeldigas/text2confl/core/upload/PageUploadOperationsImpl.kt @@ -62,29 +62,39 @@ internal class PageUploadOperationsImpl( page: Page, parentPageId: String ): PageOperationResult { + + checkTenantBeforeUpdate(confluencePageToUpdate) val (renamed, confluencePage) = adjustTitleIfRequired(confluencePageToUpdate, page) val serverPageDetails = createServerPage(confluencePage, parentPageId); - val result = if (pageContentChangeDetector.strategy(confluencePage, page.content)) { - updatePageContent(confluencePage, parentPageId, page, serverPageDetails) - } else if (confluencePage.parent?.id != parentPageId) { - changePageParent(confluencePage, parentPageId, page, serverPageDetails, confluencePageToUpdate.title) - } else if (renamed) { - PageOperationResult.LocationModified( + try { + val result = if (pageContentChangeDetector.strategy(confluencePage, page.content)) { + updatePageContent(confluencePage, parentPageId, page, serverPageDetails) + } else if (confluencePage.parent?.id != parentPageId) { + changePageParent(confluencePage, parentPageId, page, serverPageDetails, confluencePageToUpdate.title) + } else if (renamed) { + PageOperationResult.LocationModified( + page, + serverPageDetails, + parentPageId, + confluencePageToUpdate.title + ) + } else { + logger.info { "Page is up to date, nothing to do: ${confluencePage.id}, ${confluencePage.title}" } + PageOperationResult.NotModified(page, serverPageDetails) + } + setPageProperties(page, confluencePage) + return result + } catch (ex: PageNotUpdatedException) { + return PageOperationResult.Failed( page, serverPageDetails, - parentPageId, - confluencePageToUpdate.title + ex.status, + ex.body ) - } else { - logger.info { "Page is up to date, nothing to do: ${confluencePage.id}, ${confluencePage.title}" } - PageOperationResult.NotModified(page, serverPageDetails) } - setPageProperties(page, confluencePage) - - return result } private suspend fun adjustTitleIfRequired( @@ -200,21 +210,31 @@ internal class PageUploadOperationsImpl( space: String, parentPageId: String, page: Page - ): PageOperationResult.Created { + ): PageOperationResult { logger.info { "Page does not exist, need to create it: ${page.title}" } - val serverPage = client.createPage( - PageContentInput(parentPageId, page.title, page.content.body, space), - PageUpdateOptions(notifyWatchers, uploadMessage), - expansions = listOf( - "metadata.labels", - "metadata.properties.$HASH_PROPERTY", - "metadata.properties.$EDITOR_PROPERTY", - "version", - "children.attachment" + try { + val serverPage = client.createPage( + PageContentInput(parentPageId, page.title, page.content.body, space), + PageUpdateOptions(notifyWatchers, uploadMessage), + expansions = listOf( + "metadata.labels", + "metadata.properties.$HASH_PROPERTY", + "metadata.properties.$EDITOR_PROPERTY", + "version", + "children.attachment" + ) ) - ) - setPageProperties(page, serverPage) - return PageOperationResult.Created(page, createServerPage(serverPage, parentPageId)) + setPageProperties(page, serverPage) + return PageOperationResult.Created(page, createServerPage(serverPage, parentPageId)) + } catch (ex: PageNotCreatedException) { + //TODO fix parent object to something better + return PageOperationResult.Failed( + page, + serverPage = ServerPage("fake","fake","fake", emptyList(), emptyList()), + ex.status, + ex.body + ) + } } private suspend fun setPageProperties( diff --git a/core/src/test/kotlin/com/github/zeldigas/text2confl/core/ContentValidatorImplTest.kt b/core/src/test/kotlin/com/github/zeldigas/text2confl/core/ContentValidatorImplTest.kt index b9070dba..2124aa3f 100644 --- a/core/src/test/kotlin/com/github/zeldigas/text2confl/core/ContentValidatorImplTest.kt +++ b/core/src/test/kotlin/com/github/zeldigas/text2confl/core/ContentValidatorImplTest.kt @@ -23,7 +23,7 @@ internal class ContentValidatorImplTest { every { content.validate() } returns Validation.Ok every { children } returns emptyList() }) - })) + }),) } } @@ -47,7 +47,7 @@ internal class ContentValidatorImplTest { }, ) - ) + ,) }.isInstanceOf(ContentValidationFailedException::class) .transform { it.errors }.isEqualTo(listOf("${Path.of("a", "b.txt")}: err1", "c.txt: err2")) } diff --git a/core/src/test/kotlin/com/github/zeldigas/text2confl/core/config/DirectoryConfigTest.kt b/core/src/test/kotlin/com/github/zeldigas/text2confl/core/config/DirectoryConfigTest.kt index 20951fb9..c7b56ab2 100644 --- a/core/src/test/kotlin/com/github/zeldigas/text2confl/core/config/DirectoryConfigTest.kt +++ b/core/src/test/kotlin/com/github/zeldigas/text2confl/core/config/DirectoryConfigTest.kt @@ -14,8 +14,8 @@ import kotlin.io.path.writeText class DirectoryConfigTest { @Test fun `Properly read from file`(@TempDir dir: Path) { - dir.resolve(".text2confl.yml").writeText( - DirectoryConfig::class.java.getResourceAsStream("/data/.text2confl.yml")!!.reader().readText() + dir.resolve("text2confl.yml").writeText( + DirectoryConfig::class.java.getResourceAsStream("/data/text2confl.yml")!!.reader().readText() ) val config = readDirectoryConfig(dir) diff --git a/core/src/test/resources/data/.text2confl.yml b/core/src/test/resources/data/text2confl.yml similarity index 100% rename from core/src/test/resources/data/.text2confl.yml rename to core/src/test/resources/data/text2confl.yml diff --git a/docs/configuration-reference.md b/docs/configuration-reference.md index fd3a3925..1ddd8ab8 100644 --- a/docs/configuration-reference.md +++ b/docs/configuration-reference.md @@ -1,19 +1,17 @@ # Configuration reference -On this page you can find information about configuration options available in `.text2confl.yml` file as well as their +On this page you can find information about configuration options available in `text2confl.yml` file as well as their alternatives in command line or env variables format. ## Configuration options !!! warning - Keep in mind the lookup order of values: - - 1. command line argument - 2. environment variable - 3. value in `.text2confl.yml`/`.textconfl.yaml`/`text2confl.yml`/`text2confl.yaml` file (whatever is found first) - - For brevity, we will use `.text2confl.yml` in examples below. + Keep in mind the lookup order of values: + + 1. command line argument + 2. environment variable + 3. value in `text2confl.yml` | configuration file | cli option | env variable | description | |------------------------------|--------------------------------------------|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -38,7 +36,7 @@ alternatives in command line or env variables format. ### Markdown configuration options -Markdown can be configured in `.text2confl.yml` file, in `markdown` section. +Markdown can be configured in `text2confl.yml` file, in `markdown` section. Table contains available parameters. Dot (`.`) means that this is next level, e.g. @@ -74,7 +72,7 @@ markdown: ### AsciiDoc configuration options -AsciiDoc can be configured in `.text2confl.yml` file, in `asciidoc` section. +AsciiDoc can be configured in `text2confl.yml` file, in `asciidoc` section. Table contains available parameters. Dot (`.`) means that this is next level, e.g. @@ -117,7 +115,7 @@ Edit source file instead of changing page i You can specify your own text if you prefer it to be different. Note supports 2 parameters: 1. `__doc-root__` - will be replaced by value of `docs-location` option from config -2. `__file__` - will be replaced by path to file from document root (directory where `.text2confl.yml` is located) +2. `__file__` - will be replaced by path to file from document root (directory where `text2confl.yml` is located) ## Additional options for `upload` command diff --git a/docs/storage-formats/asciidoc/diagrams.adoc b/docs/storage-formats/asciidoc/diagrams.adoc index 6e87f6c2..67bce86d 100644 --- a/docs/storage-formats/asciidoc/diagrams.adoc +++ b/docs/storage-formats/asciidoc/diagrams.adoc @@ -17,7 +17,7 @@ As link:https://docs.asciidoctor.org/diagram-extension/latest/blocks/[official A With block or macro attributes, you can control name of generated file, file format and diagram-specific features. -Some attributes such as diagram file format can be convenient to configure not on page, but in `.text2confl.yml` file. +Some attributes such as diagram file format can be convenient to configure not on page, but in `text2confl.yml` file. === Inlined @@ -46,7 +46,7 @@ include::_assets/example.adoc[tag=diagram] By default, generated diagrams are saved in `.asciidoc` directory under documents root. -This is configurable with the following parameters in `.text2confl.yml` file +This is configurable with the following parameters in `text2confl.yml` file [source,yaml] ---- diff --git a/docs/storage-formats/asciidoc/toc.adoc b/docs/storage-formats/asciidoc/toc.adoc index 4204bd64..85a77f71 100644 --- a/docs/storage-formats/asciidoc/toc.adoc +++ b/docs/storage-formats/asciidoc/toc.adoc @@ -59,7 +59,7 @@ Example: `+toc::[style=square]+` will generate styled TOC in Confluence With attributes, you can control only depth of generated TOC. WARNING: AsciiDoc by default generates TOC with only first 2 levels of sections. Use `:toclevels: N` attribute to control this. -If you want to customize it for all pages do in xref:../../configuration-reference.md[`.text2confl.yml` file] +If you want to customize it for all pages do in xref:../../configuration-reference.md[`text2confl.yml` file] === AsciiDoc features that are not supported Specifying a title for the table of contents using the `toc-title` attribute is currently not supported. \ No newline at end of file diff --git a/docs/storage-formats/markdown/diagrams.md b/docs/storage-formats/markdown/diagrams.md index e6d1181d..dc4f1474 100644 --- a/docs/storage-formats/markdown/diagrams.md +++ b/docs/storage-formats/markdown/diagrams.md @@ -4,7 +4,7 @@ labels: supported-format,markdown # Markdown - diagrams -**text2confl** supports a number of text diagram formats to be embedded in Confluence page as code blocks: +**text2confl** supports a number of text diagram formats to be embedded in confluence page as code blocks: * [PlantUML](https://plantuml.com/en/) * [Mermaid](https://mermaid.js.org/) @@ -30,7 +30,7 @@ As every diagram is translated to a separate page attachment, you have two optio By default, generated diagrams are saved in `.diagrams` directory under document's root. -This is configurable with the following parameters in `.text2confl.yml` file +This is configurable with the following parameters in `text2confl.yml` file ```yaml markdown: @@ -38,10 +38,10 @@ markdown: # parameters here ``` -| name | description | default value | -|------------|---------------------------------------------------------------------------------------------------|---------------| -| `base-dir` | Base directory to store diagrams. Relative path is resolved from directory with `.text2confl.yml` | `.diagrams` | -| `temp-dir` | Use random temporary directory instead of `base-dir` | `false` | +| name | description | default value | +|------------|--------------------------------------------------------------------------------------------------|---------------| +| `base-dir` | Base directory to store diagrams. Relative path is resolved from directory with `text2confl.yml` | `.diagrams` | +| `temp-dir` | Use random temporary directory instead of `base-dir` | `false` | ## Formats @@ -86,20 +86,20 @@ Supported code-block attributes: #### Generator configuration -PlantUML parameters can be specified in `.text2confl.yml` file: +PlantUML parameters can be specified in `text2confl.yml` file: -```yaml {title=.text2confl.yml} +```yaml {title=text2confl.yml} markdown: diagrams: plantuml: # parameters here ``` -| name | description | default value | -|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------| -| `enabled` | Enable PlantUML diagrams support | `true` | -| `executable` | Command name to invoke for plantuml. There is no support for invoking a jar with `java -jar`, so you need to have a wrapper script that will do it and pass all arguments down. Relative path is resolved from directory with `.text2confl.yml` | `platuml` | -| `default-format` | Format to use for generated images. Available options: `svg`, `png` | `png` | +| name | description | default value | +|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------| +| `enabled` | Enable PlantUML diagrams support | `true` | +| `executable` | Command name to invoke for plantuml. There is no support for invoking a jar with `java -jar`, so you need to have a wrapper script that will do it and pass all arguments down. Relative path is resolved from directory with `text2confl.yml` | `platuml` | +| `default-format` | Format to use for generated images. Available options: `svg`, `png` | `png` | ### Mermaid @@ -153,9 +153,9 @@ Supported code-block attributes: #### Generator configuration -Mermaid parameters can be specified in `.text2confl.yml` file: +Mermaid parameters can be specified in `text2confl.yml` file: -```yaml {title=.text2confl.yml} +```yaml {title=text2confl.yml} markdown: diagrams: mermaid: @@ -165,11 +165,11 @@ markdown: | name | description | default value | |--------------------|----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------| | `enabled` | Enable Mermaid diagrams support | `true` | -| `executable` | Command name to invoke for mermaid. Relative path is resolved from directory with `.text2confl.yml` | `mmdc` | +| `executable` | Command name to invoke for mermaid. Relative path is resolved from directory with `text2confl.yml` | `mmdc` | | `default-format` | Format to use for generated images. Available options: `svg`, `png` | `png` | -| `config-file` | Mermaid configuration file to pass for every diagram invocation. Relative path is resolved from directory with `.text2confl.yml` | | -| `css-file` | Mermaid css file to pass for every diagram. Relative path is resolved from directory with `.text2confl.yml` | | -| `puppeeter-config` | Mermaid css file to pass for every diagram. Relative path is resolved from directory with `.text2confl.yml` | Value of `T2C_PUPPEETER_CONFIG` env variable | +| `config-file` | Mermaid configuration file to pass for every diagram invocation. Relative path is resolved from directory with `text2confl.yml` | | +| `css-file` | Mermaid css file to pass for every diagram. Relative path is resolved from directory with `text2confl.yml` | | +| `puppeeter-config` | Mermaid css file to pass for every diagram. Relative path is resolved from directory with `text2confl.yml` | Value of `T2C_PUPPEETER_CONFIG` env variable | ### Kroki @@ -233,9 +233,9 @@ Supported code-block attributes: #### Generator configuration -Kroki parameters can be specified in `.text2confl.yml` file: +Kroki parameters can be specified in `text2confl.yml` file: -```yaml {title=.text2confl.yml} +```yaml {title=text2confl.yml} markdown: diagrams: kroki: diff --git a/docs/user-guide.md b/docs/user-guide.md index 0e5ec5ed..0dedc048 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -30,8 +30,7 @@ it and will put *root* pages in it. #### Configuration file -Configuration file is `.text2confl.yml` (or `.text2confl.yml` if you prefer this extension more) - create it in -documentation root dir. +Configuration file is `text2confl.yml` create it in documentation root dir. For the start put the following contents: @@ -85,7 +84,7 @@ because corresponding documents are present: │   ├── markdown.md │   └── markdown.png ├── storage-formats.md -├── .text2confl.yml +├── text2confl.yml └── user-guide.md ``` @@ -178,7 +177,7 @@ docker run --rm -it -v ~/.config/text2confl:/root/.config/text2confl:ro -v $PWD: ## Adhoc upload -If you just need to upload one file, or you are fine with providing all the options via command line it is possible to skip creation of `.text2confl.yml` file: +If you just need to upload one file, or you are fine with providing all the options via command line it is possible to skip creation of `text2confl.yml` file: ```shell text2confl upload --docs . \ diff --git a/docs/user-guide/multitenant.md b/docs/user-guide/multitenant.md index 95164b62..72a16c2a 100644 --- a/docs/user-guide/multitenant.md +++ b/docs/user-guide/multitenant.md @@ -17,9 +17,9 @@ To solve this, you have 2 options: ## Configuring multi-tenancy -In `.text2confl.yml` add `tenant` parameter: +In `text2confl.yml` add `tenant` parameter: -```yaml {title=".text2confl.yml} +```yaml {title="text2confl.yml} tenant: team-a ``` diff --git a/pom.xml b/pom.xml index 6ecd114e..1a9c5f9a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,18 +1,19 @@ - + 4.0.0 - com.github.zeldigas.confluence - parent - 1.0.0-SNAPSHOT + io.github.text2confl + text2confl-parent + 0.16.2-SNAPSHOT pom Text2Confl - Publisher of pages in lightweight markup formats to Confluence - https://github.com/zeldigas/text2confl + + Publisher of pages in lightweight markup formats to Confluence. + Forked from https://github.com/zeldigas/text2confl to publish to maven + + https://github.com/text2confl/text2confl @@ -22,11 +23,22 @@ - scm:git:git@github.com:zeldigas/text2confl.git - scm:git:git@github.com:zeldigas/text2confl.git - https://github.com/zeldigas/text2confl + scm:git:git@github.com:text2confl/text2confl.git + scm:git:git@github.com:text2confl/text2confl.git + https://github.com/text2confl/text2confl + HEAD + + + zeldigas + Dmitry Pavlov + zeldigas@gmail.com + text2confl + https://github.com/text2confl/text2confl + + + 11 11 @@ -161,5 +173,133 @@ + + + ossrh + + + ossrh + Central Repository OSSRH + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + + + ossrh + Central Repository OSSRH + https://s01.oss.sonatype.org/content/repositories/snapshots + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.1.0 + + + sign-artifacts + verify + + sign + + + + + + + --pinentry-mode + loopback + + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.0 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.6.0 + + + attach-javadocs + + jar + + + + + + org.jetbrains.dokka + dokka-maven-plugin + 1.9.10 + + + package + + javadocJar + + + + + + org.apache.maven.plugins + maven-release-plugin + 3.0.1 + + [ci skip] + + + + + + + github + + + github + GitHub Packages + https://maven.pkg.github.com/text2confl/text2confl + + + + + coverage + + + + org.jacoco + jacoco-maven-plugin + 0.8.11 + + + prepare-agent + + prepare-agent + + + + jacoco-report + verify + + report + + + + + + + + + \ No newline at end of file