1
0
Fork 0

Full CI validation (#183)

* validateDictionaries gradle task now makes use of caching for much faster builds

* lowered the severity of missing translations linting rule 

* fixed some more errors in the translations

* added linting task to the GitHub CI validation workflow

* enabled GitHub CI validation on push to master
This commit is contained in:
Dimo Karaivanov 2023-02-13 11:17:16 +02:00 committed by GitHub
parent 4749990d44
commit 9e46213454
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 104 additions and 79 deletions

View file

@ -1,6 +1,12 @@
name: Build the Project
on: [pull_request]
on:
pull_request:
branches:
- "**"
push:
branches:
- master
jobs:
build:
@ -15,5 +21,7 @@ jobs:
# validation
- name: Validate Dictionaries
run: ./gradlew validateDictionaries
- name: Lint
run: ./gradlew lint
- name: Build Release APK
run: ./gradlew assemble
run: ./gradlew build

View file

@ -81,6 +81,89 @@ def getReleaseVersion = { ->
return "${getVersionName()} (${getCurrentGitHash()})"
}
task validateDictionaries {
inputs.dir fileTree(dir:'assets', excludes:['dict.properties'])
outputs.file "${project.buildDir}/dict.validation.txt"
doLast {
final String csvDelimiter = ' ' // TAB
String errors = ""
int errorCount = 0
final MAX_ERRORS = 50
outputs.files.singleFile.text = ""
inputs.getFiles().each {File file ->
if (errorCount >= MAX_ERRORS) {
return
}
println "Validating dictionary: " + file.name
def geographicalName = ~"[A-Z]\\w+-[^\\n]+"
def uniqueWords = [:]
int lineNumber = 0
boolean isFileValid = true
file.eachLine {line ->
if (errorCount >= MAX_ERRORS) {
return
}
lineNumber++
String[] parts = line.split(csvDelimiter, 2)
String word = parts[0]
String frequency = parts.length > 1 ? parts[1] : ""
if (frequency.length() > 0 && !frequency.matches("^\\d+\$")) {
isFileValid = false
errorCount++
errors += "Dictionary '" + file.name + "' is invalid. Found out-of-range word frequency: '" + frequency + "' on line " + lineNumber + ". Frequency must be a non-negative integer. \n"
}
if (word.matches("(\\d.+?|.+?\\d|\\d)")) {
isFileValid = false
errorCount++
errors += "Dictionary '" + file.name + "' is invalid. Found numbers on line " + lineNumber + ". Please, remove all numbers.\n"
}
if (word.matches("^\\P{L}+\$")) {
isFileValid = false
errorCount++
errors += "Dictionary '" + file.name + "' is invalid. Found a garbage word: '" + word + "' on line " + lineNumber + ".\n"
}
if (word.matches("^.\$") && !Character.isUpperCase(word.charAt(0))) {
isFileValid = false
errorCount++
errors += "Dictionary '" + file.name + "' is invalid. Found a single letter: '" + word + "' on line " + lineNumber + ". Only uppercase single letters are allowed. The rest of the alphabet will be added automatically.\n"
}
String uniqueWordKey = word ==~ geographicalName ? word : word.toLowerCase()
if (uniqueWords[uniqueWordKey] != null && uniqueWords[uniqueWordKey] == true) {
isFileValid = false
errorCount++
errors += "Dictionary '" + file.name + "' is invalid. Found a repeating word: '" + word + "' on line " + lineNumber + ". Ensure all words appear only once.\n"
} else {
uniqueWords[uniqueWordKey] = true
}
if (errorCount >= MAX_ERRORS ) {
errors += "Too many errors! Aborting.\n"
}
}
outputs.files.singleFile.text += file.name + " " + (isFileValid ? "OK" : "INVALID") + "\n"
}
if (errors != "") {
throw new GradleException(errors)
}
}
}
android {
buildToolsVersion "33.0.0"
@ -143,78 +226,8 @@ android {
// signingConfig android.signingConfigs.release
}
}
}
task validateDictionaries {
inputs.dir fileTree(dir:'assets', excludes:['dict.properties'])
doLast {
final String csvDelimiter = ' '
String errors = ""
int errorCount = 0
final MAX_ERRORS = 50
inputs.getFiles().each {File file ->
if (errorCount >= MAX_ERRORS) {
return
}
println "Validating dictionary: " + file.name
def geographicalName = ~"[A-Z]\\w+-[^\\n]+"
def uniqueWords = [:]
int lineNumber = 0
file.eachLine {line ->
if (errorCount >= MAX_ERRORS) {
return
}
lineNumber++
String[] parts = line.split(csvDelimiter, 2)
String word = parts[0]
String frequency = parts.length > 1 ? parts[1] : ""
if (frequency.length() > 0 && !frequency.matches("^\\d+\$")) {
errorCount++
errors += "Dictionary '" + file.name + "' is invalid. Found out-of-range word frequency: '" + frequency + "' on line " + lineNumber + ". Frequency must be a non-negative integer. \n"
}
if (word.matches("(\\d.+?|.+?\\d|\\d)")) {
errorCount++
errors += "Dictionary '" + file.name + "' is invalid. Found numbers on line " + lineNumber + ". Please, remove all numbers.\n"
}
if (word.matches("^\\P{L}+\$")) {
errorCount++
errors += "Dictionary '" + file.name + "' is invalid. Found a garbage word: '" + word + "' on line " + lineNumber + ".\n"
}
if (word.matches("^.\$") && !Character.isUpperCase(word.charAt(0))) {
errorCount++
errors += "Dictionary '" + file.name + "' is invalid. Found a single letter: '" + word + "' on line " + lineNumber + ". Only uppercase single letters are allowed. The rest of the alphabet will be added automatically.\n"
}
String uniqueWordKey = word ==~ geographicalName ? word : word.toLowerCase()
if (uniqueWords[uniqueWordKey] != null && uniqueWords[uniqueWordKey] == true) {
errorCount++
errors += "Dictionary '" + file.name + "' is invalid. Found a repeating word: '" + word + "' on line " + lineNumber + ". Ensure all words appear only once.\n"
} else {
uniqueWords[uniqueWordKey] = true
}
if (errorCount >= MAX_ERRORS ) {
errors += "Too many errors! Aborting.\n"
}
}
}
if (errors != "") {
throw new GradleException(errors)
}
applicationVariants.all { variant ->
tasks["merge${variant.name.capitalize()}Assets"].dependsOn(validateDictionaries)
}
}
preBuild.dependsOn validateDictionaries
preBuild.mustRunAfter validateDictionaries

4
lint.xml Normal file
View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="MissingTranslation" severity="warning" />
</lint>

View file

@ -49,5 +49,5 @@
<string name="pref_double_zero_char">Символ при многократно натисната \"0\"</string>
<string name="char_newline">Нов ред</string>
<string name="char_space">Интервал</string>
<string name="add_word_field_placeholder">Напишете дума...</string>
<string name="add_word_field_placeholder">Напишете дума</string>
</resources>

View file

@ -40,5 +40,5 @@
<string name="char_space">Espace</string>
<string name="char_newline">Nouvelle ligne</string>
<string name="pref_double_zero_char">Caractère lorsque «0» est appuyé plusieurs fois</string>
<string name="add_word_field_placeholder">Tapez un mot...</string>
<string name="add_word_field_placeholder">Tapez un mot</string>
</resources>

View file

@ -49,5 +49,5 @@
<string name="key_hold_key">(зажать)</string>
<string name="key_back">Назад</string>
<string name="key_call">Позвонить</string>
<string name="add_word_field_placeholder">Введите слово...</string>
<string name="add_word_field_placeholder">Введите слово</string>
</resources>

View file

@ -49,5 +49,5 @@
<string name="dictionary_truncated">Словник успішно очищено.</string>
<string name="dictionary_missing_go_load_it">Немає словника для мови «%1$s». Перейдіть до Налаштувань, щоб завантажити його.</string>
<string name="dictionary_load_bad_char">Помилка завантаження. Недійсне слово «%1$s» в рядку %2$d мови «%3$s».</string>
<string name="add_word_field_placeholder">Введіть слово...</string>
<string name="add_word_field_placeholder">Введіть слово</string>
</resources>

View file

@ -11,7 +11,7 @@
<string name="add_word_exist">Word \"%1$s\" already in the dictionary.</string>
<string name="add_word_invalid_language" translatable="false">Cannot add a word when no language is selected.</string>
<string name="add_word_title">Add Word</string>
<string name="add_word_field_placeholder">Type a word...</string>
<string name="add_word_field_placeholder">Type a word</string>
<string name="pref_category_about">About</string>
<string name="pref_category_appearance">Appearance</string>