From 13efcfa4e4b40972ab2d951790cb2eea71b44e6d Mon Sep 17 00:00:00 2001 From: Rd Date: Wed, 10 Apr 2024 23:51:08 +0530 Subject: [PATCH] Script to Check the Table of Contents in the Wiki and CI workflow --- .github/workflows/wiki.yml | 18 ++++ scripts/BUILD.bazel | 7 ++ .../oppia/android/scripts/wiki/BUILD.bazel | 18 ++++ .../scripts/wiki/WikiTableOfContentsCheck.kt | 93 +++++++++++++++++++ 4 files changed, 136 insertions(+) create mode 100644 scripts/src/java/org/oppia/android/scripts/wiki/BUILD.bazel create mode 100644 scripts/src/java/org/oppia/android/scripts/wiki/WikiTableOfContentsCheck.kt diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml index f37031f61e9..66ab33d7ca3 100644 --- a/.github/workflows/wiki.yml +++ b/.github/workflows/wiki.yml @@ -10,6 +10,24 @@ on: gollum: jobs: + table_of_contents_check: + name: Wiki Check Table of Contents match Headers + runs-on: ubuntu-20.04 + env: + CACHE_DIRECTORY: ~/.bazel_cache + steps: + - uses: actions/checkout@v2 + + - name: Set up Bazel + uses: abhinavsingh/setup-bazel@v3 + with: + version: 4.0.0 + + - name: Wiki Check Table of Contents + id: wikichecktoc + run: | + bazel run //scripts:wiki_table_of_contents_check -- ${GITHUB_WORKSPACE} + wiki-deploy: runs-on: ${{ matrix.os }} strategy: diff --git a/scripts/BUILD.bazel b/scripts/BUILD.bazel index 01390e600e7..b404a09ef61 100644 --- a/scripts/BUILD.bazel +++ b/scripts/BUILD.bazel @@ -227,6 +227,13 @@ kt_jvm_binary( ], ) +kt_jvm_binary( + name = "wiki_table_of_contents_check", + testonly = True, + main_class = "org.oppia.android.scripts.wiki.WikiTableOfContentsCheckKt", + runtime_deps = ["//scripts/src/java/org/oppia/android/scripts/wiki:wiki_table_of_contents_check_lib"], +) + # Note that this is intentionally not test-only since it's used by the app build pipeline. Also, # this apparently needs to be a java_binary to set up runfiles correctly when executed within a # Starlark rule as a tool. diff --git a/scripts/src/java/org/oppia/android/scripts/wiki/BUILD.bazel b/scripts/src/java/org/oppia/android/scripts/wiki/BUILD.bazel new file mode 100644 index 00000000000..6898d1b8c21 --- /dev/null +++ b/scripts/src/java/org/oppia/android/scripts/wiki/BUILD.bazel @@ -0,0 +1,18 @@ +""" +Libraries corresponding to scripting tools that help with continuous integration workflows. +""" + +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library") + +kt_jvm_library( + name = "wiki_table_of_contents_check_lib", + testonly = True, + srcs = [ + "WikiTableOfContentsCheck.kt", + ], + visibility = ["//scripts:oppia_script_binary_visibility"], + deps = [ + "//scripts/src/java/org/oppia/android/scripts/common:bazel_client", + "//scripts/src/java/org/oppia/android/scripts/common:git_client", + ], +) diff --git a/scripts/src/java/org/oppia/android/scripts/wiki/WikiTableOfContentsCheck.kt b/scripts/src/java/org/oppia/android/scripts/wiki/WikiTableOfContentsCheck.kt new file mode 100644 index 00000000000..eeadf710480 --- /dev/null +++ b/scripts/src/java/org/oppia/android/scripts/wiki/WikiTableOfContentsCheck.kt @@ -0,0 +1,93 @@ +package org.oppia.android.scripts.wiki + +import java.io.File + +/** + * Script for ensuring that the table of contents in each wiki page matches with its respective headers. + * + * Usage: + * bazel run //scripts:wiki_sample -- + * + * Arguments: + * - path_to_default_working_directory: The default working directory on the runner for steps, and the default location of repository. + * + * Example: + * bazel run //scripts:wiki_sample -- ${GITHUB_WORKSPACE} + */ +fun main(vararg args: String) { + // Path to the repo's wiki. + val githubWorkspace = "${args[0]}/wiki/" + val wikiDirectory = File(githubWorkspace) + + // Check if the wiki directory exists + if (wikiDirectory.exists() && wikiDirectory.isDirectory) { + processWikiDirectory(wikiDirectory) + } else { + println("No contents found in the Wiki directory.") + } +} + +/** + * Checks every file in the wiki repo + * + * @param wikiDirectory the default working directory + */ +fun processWikiDirectory(wikiDirectory: File) { + wikiDirectory.listFiles()?.forEach { file -> + processWikiFile(file) + } +} + +/** + * Processes the contents of a single wiki file to ensure the accuracy of the Table of Contents. + * + * @param file The wiki file to process. + */ +fun processWikiFile(file: File) { + var inTableOfContents = false + var skipBlankLine = false + + file.forEachLine { line -> + when { + // Checking for Table of Contents section + line.trim() == "## Table of Contents" -> { + inTableOfContents = true + skipBlankLine = true + } + // Checking to skip the blank line immediately after the ## Table of Contents + skipBlankLine && line.isBlank() -> skipBlankLine = false + // Validating the contents in the Table of Content + inTableOfContents && line.trimStart().startsWith("- [") && !line.contains("https://") -> { + validateTableOfContents(file, line) + } + // Checking for end of Table of Contents section + inTableOfContents && line.isBlank() -> inTableOfContents = false + } + } +} + +/** + * Validates the accuracy of a Table of Contents entry in a wiki file. + * + * @param file The wiki file being validated. + * @param line The line containing the Table of Contents entry. + */ +fun validateTableOfContents(file: File, line: String) { + val titleRegex = "\\[(.*?)\\]".toRegex() + val title = titleRegex.find(line)?.groupValues?.get(1)?.replace('-', ' ') + ?.replace(Regex("[?&./:’'*!,(){}\\[\\]+]"), "") + ?.trim() + + val linkRegex = "\\(#(.*?)\\)".toRegex() + val link = linkRegex.find(line)?.groupValues?.get(1)?.removePrefix("#")?.replace('-', ' ') + ?.replace(Regex("[?&./:’'*!,(){}\\[\\]+]"), "") + ?.replace("confetti_ball", "")?.trim() + + // Checks if the table of content title matches with the header link text + val matches = title.equals(link, ignoreCase = true) + if (!matches) { + throw Exception("\nMismatch of Table of Content with headers in the File: ${file.name}. " + + "\nThe Title: '${titleRegex.find(line)?.groupValues?.get(1)}' " + + "doesn't match with its corresponding Link: '${linkRegex.find(line)?.groupValues?.get(1)}'.") + } +}