-
Notifications
You must be signed in to change notification settings - Fork 521
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
<!-- READ ME FIRST: Please fill in the explanation section below and check off every point from the Essential Checklist! --> ## Explanation Fixes #5015 This introduces a script to verify that the table of contents in each wiki page corresponds to its headers whenever a wiki change is made, ensuring consistency and correctness in documentation. Note: Ensure any emojis included in the headers are also reflected in the table of contents. ## Essential Checklist <!-- Please tick the relevant boxes by putting an "x" in them. --> - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)).
- Loading branch information
Showing
11 changed files
with
358 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
scripts/src/java/org/oppia/android/scripts/wiki/BUILD.bazel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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", | ||
], | ||
) |
82 changes: 82 additions & 0 deletions
82
scripts/src/java/org/oppia/android/scripts/wiki/WikiTableOfContentsCheck.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
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_table_of_contents_check -- <path_to_default_working_directory> | ||
* | ||
* 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_table_of_contents_check -- $(pwd) | ||
*/ | ||
fun main(vararg args: String) { | ||
// Path to the repo's wiki. | ||
val wikiDirPath = "${args[0]}/wiki/" | ||
val wikiDir = File(wikiDirPath) | ||
|
||
// Check if the wiki directory exists. | ||
if (wikiDir.exists() && wikiDir.isDirectory) { | ||
processWikiDirectory(wikiDir) | ||
println("WIKI TABLE OF CONTENTS CHECK PASSED") | ||
} else { | ||
println("No contents found in the Wiki directory.") | ||
} | ||
} | ||
|
||
private fun processWikiDirectory(wikiDir: File) { | ||
wikiDir.listFiles()?.forEach { file -> | ||
checkTableOfContents(file) | ||
} | ||
} | ||
|
||
private fun checkTableOfContents(file: File) { | ||
val fileContents = file.readLines() | ||
val tocStartIdx = fileContents.indexOfFirst { | ||
it.contains(Regex("""##\s+Table\s+of\s+Contents""", RegexOption.IGNORE_CASE)) | ||
} | ||
if (tocStartIdx == -1) { | ||
return | ||
} | ||
|
||
// Skipping the blank line after the ## Table of Contents | ||
val tocEndIdx = fileContents.subList(tocStartIdx + 2, fileContents.size).indexOfFirst { | ||
it.startsWith("#") | ||
}.takeIf { it != -1 } | ||
?: error("Wiki doesn't contain headers referenced in Table of Contents.") | ||
|
||
val tocSpecificLines = fileContents.subList(tocStartIdx, tocStartIdx + tocEndIdx + 1) | ||
|
||
for (line in tocSpecificLines) { | ||
if (line.trimStart().startsWith("- [") && !line.contains("https://")) { | ||
validateTableOfContents(file, line) | ||
} | ||
} | ||
} | ||
|
||
private 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("[?&./:’'*!,(){}\\[\\]+]"), "") | ||
?.trim() | ||
|
||
// Checks if the table of content title matches with the header link text. | ||
val matches = title.equals(link, ignoreCase = true) | ||
if (!matches) { | ||
error( | ||
"\nWIKI TABLE OF CONTENTS CHECK FAILED" + | ||
"\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)}'." | ||
) | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
scripts/src/javatests/org/oppia/android/scripts/wiki/BUILD.bazel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
""" | ||
Tests corresponding to wiki-related checks. | ||
""" | ||
|
||
load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_test") | ||
|
||
kt_jvm_test( | ||
name = "WikiTableOfContentsCheckTest", | ||
srcs = ["WikiTableOfContentsCheckTest.kt"], | ||
deps = [ | ||
"//scripts/src/java/org/oppia/android/scripts/wiki:wiki_table_of_contents_check_lib", | ||
"//testing:assertion_helpers", | ||
"//third_party:com_google_truth_truth", | ||
"//third_party:org_jetbrains_kotlin_kotlin-test-junit", | ||
], | ||
) |
190 changes: 190 additions & 0 deletions
190
scripts/src/javatests/org/oppia/android/scripts/wiki/WikiTableOfContentsCheckTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
package org.oppia.android.scripts.wiki | ||
|
||
import com.google.common.truth.Truth.assertThat | ||
import org.junit.After | ||
import org.junit.Before | ||
import org.junit.Rule | ||
import org.junit.Test | ||
import org.junit.rules.TemporaryFolder | ||
import org.oppia.android.testing.assertThrows | ||
import java.io.ByteArrayOutputStream | ||
import java.io.PrintStream | ||
|
||
/** Tests for [WikiTableOfContentsCheck]. */ | ||
class WikiTableOfContentsCheckTest { | ||
private val outContent: ByteArrayOutputStream = ByteArrayOutputStream() | ||
private val originalOut: PrintStream = System.out | ||
private val WIKI_TOC_CHECK_PASSED_OUTPUT_INDICATOR = "WIKI TABLE OF CONTENTS CHECK PASSED" | ||
private val WIKI_TOC_CHECK_FAILED_OUTPUT_INDICATOR = "WIKI TABLE OF CONTENTS CHECK FAILED" | ||
|
||
@field:[Rule JvmField] val tempFolder = TemporaryFolder() | ||
|
||
@Before | ||
fun setUp() { | ||
System.setOut(PrintStream(outContent)) | ||
} | ||
|
||
@After | ||
fun tearDown() { | ||
System.setOut(originalOut) | ||
} | ||
|
||
@Test | ||
fun testWikiTOCCheck_noWikiDirExists_printsNoContentFound() { | ||
runScript() | ||
assertThat(outContent.toString().trim()).isEqualTo("No contents found in the Wiki directory.") | ||
} | ||
|
||
@Test | ||
fun testWikiTOCCheck_noWikiDirectory_printsNoContentFound() { | ||
tempFolder.newFile("wiki") | ||
runScript() | ||
assertThat(outContent.toString().trim()).isEqualTo("No contents found in the Wiki directory.") | ||
} | ||
|
||
@Test | ||
fun testWikiTOCCheck_validWikiTOC_checkPass() { | ||
tempFolder.newFolder("wiki") | ||
val file = tempFolder.newFile("wiki/wiki.md") | ||
file.writeText( | ||
""" | ||
## Table of Contents | ||
- [Introduction](#introduction) | ||
- [Usage](#usage) | ||
## Introduction | ||
Content | ||
## Usage | ||
Content | ||
""".trimIndent() | ||
) | ||
|
||
runScript() | ||
|
||
assertThat(outContent.toString().trim()).contains(WIKI_TOC_CHECK_PASSED_OUTPUT_INDICATOR) | ||
} | ||
|
||
@Test | ||
fun testWikiTOCCheck_missingWikiTOC_returnsNoTOCFound() { | ||
tempFolder.newFolder("wiki") | ||
val file = tempFolder.newFile("wiki/wiki.md") | ||
file.writeText( | ||
""" | ||
- [Introduction](#introduction) | ||
- [Usage](#usage) | ||
## Introduction | ||
Content | ||
## Usage | ||
Content | ||
""".trimIndent() | ||
) | ||
|
||
runScript() | ||
|
||
assertThat(outContent.toString().trim()).contains(WIKI_TOC_CHECK_PASSED_OUTPUT_INDICATOR) | ||
} | ||
|
||
@Test | ||
fun testWikiTOCCheck_wikiTOCReference_noHeadersFound_throwsException() { | ||
tempFolder.newFolder("wiki") | ||
val file = tempFolder.newFile("wiki/wiki.md") | ||
file.writeText( | ||
""" | ||
## Table of Contents | ||
- [Introduction](#introductions) | ||
""".trimIndent() | ||
) | ||
|
||
val exception = assertThrows<IllegalStateException>() { | ||
runScript() | ||
} | ||
|
||
assertThat(exception).hasMessageThat().contains( | ||
"Wiki doesn't contain headers referenced in Table of Contents." | ||
) | ||
} | ||
|
||
@Test | ||
fun testWikiTOCCheck_mismatchWikiTOC_checkFail() { | ||
tempFolder.newFolder("wiki") | ||
val file = tempFolder.newFile("wiki/wiki.md") | ||
file.writeText( | ||
""" | ||
## Table of Contents | ||
- [Introduction](#introductions) | ||
- [Usage](#usage) | ||
## Introduction | ||
Content | ||
## Usage | ||
Content | ||
""".trimIndent() | ||
) | ||
|
||
val exception = assertThrows<IllegalStateException>() { | ||
runScript() | ||
} | ||
|
||
assertThat(exception).hasMessageThat().contains(WIKI_TOC_CHECK_FAILED_OUTPUT_INDICATOR) | ||
} | ||
|
||
@Test | ||
fun testWikiTOCCheck_validWikiTOCWithSeparator_checkPass() { | ||
tempFolder.newFolder("wiki") | ||
val file = tempFolder.newFile("wiki/wiki.md") | ||
file.writeText( | ||
""" | ||
## Table of Contents | ||
- [Introduction To Wiki](#introduction-to-wiki) | ||
- [Usage Wiki-Content](#usage-wiki-content) | ||
## Introduction | ||
Content | ||
## Usage | ||
Content | ||
""".trimIndent() | ||
) | ||
|
||
runScript() | ||
|
||
assertThat(outContent.toString().trim()).contains(WIKI_TOC_CHECK_PASSED_OUTPUT_INDICATOR) | ||
} | ||
|
||
@Test | ||
fun testWikiTOCCheck_validWikiTOCWithSpecialCharacter_checkPass() { | ||
tempFolder.newFolder("wiki") | ||
val file = tempFolder.newFile("wiki/wiki.md") | ||
file.writeText( | ||
""" | ||
## Table of Contents | ||
- [Introduction](#introduction?) | ||
- [Usage?](#usage) | ||
## Introduction | ||
Content | ||
## Usage | ||
Content | ||
""".trimIndent() | ||
) | ||
|
||
runScript() | ||
|
||
assertThat(outContent.toString().trim()).contains(WIKI_TOC_CHECK_PASSED_OUTPUT_INDICATOR) | ||
} | ||
|
||
private fun runScript() { | ||
main(tempFolder.root.absolutePath) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.