From fd6b77d7c815dbb3bb6f5b70a4d7052083754c52 Mon Sep 17 00:00:00 2001 From: davidacm Date: Tue, 2 May 2023 05:59:42 -0600 Subject: [PATCH] added a script to update version in the buildVars.py automatically if it as not been changed but is confirmed in a commit. this can be used manually, or automatically with a post commit hook. now *.json files are ignored. updated sconstruct to generate the required json file for the add-on store. updated documentation. updated buildVars to be compatible with the new sconstruct. added turkish language. updated the github workflow to generate the required json for the nvda add-on store. --- .github/workflows/upload-on-tag.yaml | 8 +- .gitignore | 2 + addon/doc/tr/readme.md | 35 ++++ addon/locale/tr/LC_MESSAGES/nvda.po | 27 ++++ buildVars.py | 16 ++ readme.md | 2 +- sconstruct | 232 +++++++++++++++++++++------ updateVersion.py | 13 ++ 8 files changed, 286 insertions(+), 49 deletions(-) create mode 100644 addon/doc/tr/readme.md create mode 100644 addon/locale/tr/LC_MESSAGES/nvda.po create mode 100644 updateVersion.py diff --git a/.github/workflows/upload-on-tag.yaml b/.github/workflows/upload-on-tag.yaml index 5ef32eb..49641b3 100644 --- a/.github/workflows/upload-on-tag.yaml +++ b/.github/workflows/upload-on-tag.yaml @@ -46,7 +46,9 @@ jobs: - uses: actions/upload-artifact@v3 with: name: packaged_addon - path: ./*.nvda-addon + path: | + ./*.nvda-addon + ./*.json upload_release: runs-on: ubuntu-latest @@ -62,6 +64,8 @@ jobs: - name: Release uses: softprops/action-gh-release@v1 with: - files: packaged_addon/*.nvda-addon + files: | + packaged_addon/*.nvda-addon + packaged_addon/*.json fail_on_unmatched_files: true prerelease: ${{ contains(github.ref, '-') }} diff --git a/.gitignore b/.gitignore index a5b7b11..172e42b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ addon/doc/en/ *.py[co] *.nvda-addon .sconsign.dblite +*.code-workspace +*.json \ No newline at end of file diff --git a/addon/doc/tr/readme.md b/addon/doc/tr/readme.md new file mode 100644 index 0000000..6161c83 --- /dev/null +++ b/addon/doc/tr/readme.md @@ -0,0 +1,35 @@ +# Kulaklık Bulucu NVDA Eklentisi # +Bu eklenti, sol veya sağ kulaklıktan bir bip sesi çıkaran iki komuta sahiptir. Bluetooth kulaklığınızı bulmanıza yardımcı olması için amaçlanmıştır. Ton 5 dakika çalacaktır. Tonu durdurmanız gerekirse, atanan hareketlerden herhangi birine basın. Daha fazla zamana ihtiyacınız varsa, gerekli tuşuna iki defa bsın. + +Telif Hakkı (C) 2023 David CM + +Bu paket, GNU Genel Kamu Lisansı, sürüm 2 veya sonraki koşulları altında dağıtılmaktadır. + + +## Kullanım: + +* Sol kulaklığı bulmak için alt + control + nvda + j tuşlarına basın. +* Sağ kulaklığı bulmak için alt + control + nvda + k tuşlarına basın. +* Bip sesini durdurmak için, basmış olduğunuz kısayol tuşuna tekrar basın. +* ton durursa ve kulaklığınızı henüz bulamadıysanız, gereken harekete iki kez basın. + +## Gereksinimler: + NVDA 2018.2 veya sonrasına ihtiyacınız var. + +## Kurulum: + Sadece bir NVDA eklentisi olarak kurun. + +## katkılar, raporlar ve bağışlar: + +Projemi beğendiyseniz veya bu yazılım günlük hayatınızda işinize yararsa ve bir şekilde katkıda bulunmak isterseniz aşağıdaki yöntemlerle bağışta bulunabilirsiniz: + +* [PayPal.](https://paypal.me/davicm) +* [Ko-fi.](https://ko-fi.com/davidacm) +* [kripto para birimleri ve diğer yöntemler.](https://davidacm.github.io/donations/) + +Hataları düzeltmek, sorunları bildirmek veya yeni özellikler istiyorsanız benimle adresinden iletişime geçebilirsiniz. + + Veya bu projenin github deposunda: + [GitHub'da Kulaklık Bulucu](https://github.com/davidacm/NVDA-HeadphoneFinder) + + Bu eklentinin en son sürümünü bu depodan edinebilirsiniz. diff --git a/addon/locale/tr/LC_MESSAGES/nvda.po b/addon/locale/tr/LC_MESSAGES/nvda.po new file mode 100644 index 0000000..7383b77 --- /dev/null +++ b/addon/locale/tr/LC_MESSAGES/nvda.po @@ -0,0 +1,27 @@ +msgid "" +msgstr "" +"Project-Id-Version: Kulaklık Bulucu 0.1\n" +"POT-Creation-Date: 2023-03-13 21:29+0300\n" +"PO-Revision-Date: 2023-03-13 21:30+0300\n" +"Last-Translator: Umut KORKMAZ \n" +"Language-Team: Umut KORKMAZ \n" +"Language: tr_TR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.2.2\n" +"X-Poedit-Basepath: ../../../globalPlugins\n" +"X-Poedit-SearchPath-0: .\n" + +#: headphoneFinder.py:15 +msgid "Headphone Finder" +msgstr "Kulaklık Bulucu" + +#: headphoneFinder.py:31 +msgid "plays a tone at the left speaker." +msgstr "sol hoparlörde bir ton çalar." + +#: headphoneFinder.py:38 +msgid "plays a tone at the right speaker." +msgstr "sağ hoparlörde bir ton çalar." diff --git a/buildVars.py b/buildVars.py index f278463..278e0cd 100644 --- a/buildVars.py +++ b/buildVars.py @@ -32,6 +32,12 @@ "addon_lastTestedNVDAVersion" : "2023.1.0", # Add-on update channel (default is stable or None) "addon_updateChannel" : None, + # Add-on license such as GPL 2 + "addon_license": "GPL 2", + # URL for the license document the ad-on is licensed under + "addon_licenseURL": "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html", + # URL for the add-on repository where the source code can be found + "addon_sourceURL": "https://github.com/davidacm/NVDA-HeadphoneFinder", } from os import path @@ -46,3 +52,13 @@ # Files that will be ignored when building the nvda-addon file # Paths are relative to the addon directory, not to the root directory of your addon sources. excludedFiles = [] + +# Base language for the NVDA add-on +baseLanguage = "en" + +# Markdown extensions for add-on documentation +# Most add-ons do not require additional Markdown extensions. +# If you need to add support for markup such as tables, fill out the below list. +# Extensions string must be of the form "markdown.extensions.extensionName" +# e.g. "markdown.extensions.tables" to add tables. +markdownExtensions = [] diff --git a/readme.md b/readme.md index 0736da2..c9dc81f 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@ Copyright (C) 2023 David CM This package is distributed under the terms of the GNU General Public License, version 2 or later. ## Download. - The latest release is available to [download in this link](https://davidacm.github.io/getlatest/gh/davidacm/NVDA-HeadphoneFinder) + The latest release is available to [download in this link](https://davidacm.github.io/getlatest/gh/davidacm/NVDA-HeadphoneFinder/?index=1) ## Usage diff --git a/sconstruct b/sconstruct index 345ef5a..8aa914b 100644 --- a/sconstruct +++ b/sconstruct @@ -1,19 +1,41 @@ # NVDA add-on template SCONSTRUCT file -#Copyright (C) 2012, 2014 Rui Batista -#This file is covered by the GNU General Public License. -#See the file COPYING.txt for more details. +# Copyright (C) 2012-2023 Rui Batista, Noelia Martinez, Joseph Lee +# This file is covered by the GNU General Public License. +# See the file COPYING.txt for more details. import codecs import gettext import os import os.path import zipfile -import buildVars +import sys + +# While names imported below are available by default in every SConscript +# Linters aren't aware about them. +# To avoid Flake8 F821 warnings about them they are imported explicitly. +# When using other Scons functions please add them to the line below. +from SCons.Script import BoolVariable, Builder, Copy, Environment, Variables + +sys.dont_write_bytecode = True + +# Bytecode should not be written for build vars module to keep the repository root folder clean. +import buildVars # NOQA: E402 + def md2html(source, dest): import markdown + # Use extensions if defined. + mdExtensions = buildVars.markdownExtensions lang = os.path.basename(os.path.dirname(source)).replace('_', '-') - title="{addonSummary} {addonVersion}".format(addonSummary=buildVars.addon_info["addon_summary"], addonVersion=buildVars.addon_info["addon_version"]) + localeLang = os.path.basename(os.path.dirname(source)) + try: + _ = gettext.translation("nvda", localedir=os.path.join("addon", "locale"), languages=[localeLang]).gettext + summary = _(buildVars.addon_info["addon_summary"]) + except Exception: + summary = buildVars.addon_info["addon_summary"] + title = "{addonSummary} {addonVersion}".format( + addonSummary=summary, addonVersion=buildVars.addon_info["addon_version"] + ) headerDic = { "[[!meta title=\"": "# ", "\"]]": " #", @@ -22,35 +44,52 @@ def md2html(source, dest): mdText = f.read() for k, v in headerDic.items(): mdText = mdText.replace(k, v, 1) - htmlText = markdown.markdown(mdText) + htmlText = markdown.markdown(mdText, extensions=mdExtensions) + # Optimization: build resulting HTML text in one go instead of writing parts separately. + docText = "\n".join([ + "", + "" % lang, + "", + "" + "", + "", + "%s" % title, + "\n", + htmlText, + "\n" + ]) with codecs.open(dest, "w", "utf-8") as f: - f.write("\n" + - "\n" + - "\n" % (lang, lang) + - "\n" + - "\n" + - "\n" + - "%s\n" % title + - "\n\n" - ) - f.write(htmlText) - f.write("\n\n") + f.write(docText) + def mdTool(env): - mdAction=env.Action( - lambda target,source,env: md2html(source[0].path, target[0].path), - lambda target,source,env: 'Generating %s'%target[0], + mdAction = env.Action( + lambda target, source, env: md2html(source[0].path, target[0].path), + lambda target, source, env: 'Generating % s' % target[0], ) - mdBuilder=env.Builder( + mdBuilder = env.Builder( action=mdAction, suffix='.html', src_suffix='.md', ) - env['BUILDERS']['markdown']=mdBuilder + env['BUILDERS']['markdown'] = mdBuilder + + +def validateVersionNumber(key, val, env): + # Used to make sure version major.minor.patch are integers to comply with NV Access add-on store. + # Ignore all this if version number is not specified, in which case json generator will validate this info. + if val == "0.0.0": + return + versionNumber = val.split(".") + if len(versionNumber) < 3: + raise ValueError("versionNumber must have three parts (major.minor.patch)") + if not all([part.isnumeric() for part in versionNumber]): + raise ValueError("versionNumber (major.minor.patch) must be integers") + vars = Variables() vars.Add("version", "The version of this build", buildVars.addon_info["addon_version"]) +vars.Add("versionNumber", "Version number of the form major.minor.patch", "0.0.0", validateVersionNumber) vars.Add(BoolVariable("dev", "Whether this is a daily development version", False)) vars.Add("channel", "Update channel for this build", buildVars.addon_info["addon_updateChannel"]) @@ -61,47 +100,64 @@ if env["dev"]: import datetime buildDate = datetime.datetime.now() year, month, day = str(buildDate.year), str(buildDate.month), str(buildDate.day) - env["addon_version"] = "".join([year, month.zfill(2), day.zfill(2), "-dev"]) + versionTimestamp = "".join([year, month.zfill(2), day.zfill(2)]) + env["addon_version"] = f"{versionTimestamp}-dev" + env["versionNumber"] = f"{versionTimestamp}.0.0" env["channel"] = "dev" elif env["version"] is not None: env["addon_version"] = env["version"] if "channel" in env and env["channel"] is not None: env["addon_updateChannel"] = env["channel"] +buildVars.addon_info["addon_version"] = env["addon_version"] +buildVars.addon_info["addon_updateChannel"] = env["addon_updateChannel"] + addonFile = env.File("${addon_name}-${addon_version}.nvda-addon") + def addonGenerator(target, source, env, for_signature): - action = env.Action(lambda target, source, env : createAddonBundleFromPath(source[0].abspath, target[0].abspath) and None, - lambda target, source, env : "Generating Addon %s" % target[0]) + action = env.Action( + lambda target, source, env: createAddonBundleFromPath(source[0].abspath, target[0].abspath) and None, + lambda target, source, env: "Generating Addon %s" % target[0] + ) return action + def manifestGenerator(target, source, env, for_signature): - action = env.Action(lambda target, source, env : generateManifest(source[0].abspath, target[0].abspath) and None, - lambda target, source, env : "Generating manifest %s" % target[0]) + action = env.Action( + lambda target, source, env: generateManifest(source[0].abspath, target[0].abspath) and None, + lambda target, source, env: "Generating manifest %s" % target[0] + ) return action + def translatedManifestGenerator(target, source, env, for_signature): dir = os.path.abspath(os.path.join(os.path.dirname(str(source[0])), "..")) lang = os.path.basename(dir) - action = env.Action(lambda target, source, env : generateTranslatedManifest(source[1].abspath, lang, target[0].abspath) and None, - lambda target, source, env : "Generating translated manifest %s" % target[0]) + action = env.Action( + lambda target, source, env: generateTranslatedManifest(source[1].abspath, lang, target[0].abspath) and None, + lambda target, source, env: "Generating translated manifest %s" % target[0] + ) return action + env['BUILDERS']['NVDAAddon'] = Builder(generator=addonGenerator) env['BUILDERS']['NVDAManifest'] = Builder(generator=manifestGenerator) env['BUILDERS']['NVDATranslatedManifest'] = Builder(generator=translatedManifestGenerator) + def createAddonHelp(dir): docsDir = os.path.join(dir, "doc") if os.path.isfile("style.css"): cssPath = os.path.join(docsDir, "style.css") cssTarget = env.Command(cssPath, "style.css", Copy("$TARGET", "$SOURCE")) env.Depends(addon, cssTarget) - if os.path.isfile("addonReadme.md"): - readmePath = os.path.join(docsDir, "en", "readme.md") - readmeTarget = env.Command(readmePath, "addonReadme.md", Copy("$TARGET", "$SOURCE")) + if os.path.isfile("readme.md"): + readmePath = os.path.join(docsDir, buildVars.baseLanguage, "readme.md") + readmeTarget = env.Command(readmePath, "readme.md", Copy("$TARGET", "$SOURCE")) env.Depends(addon, readmeTarget) + def createAddonBundleFromPath(path, dest): """ Creates a bundle from a directory that contains an addon manifest file.""" basedir = os.path.abspath(path) @@ -112,19 +168,96 @@ def createAddonBundleFromPath(path, dest): for filename in filenames: pathInBundle = os.path.join(relativePath, filename) absPath = os.path.join(dir, filename) - if pathInBundle not in buildVars.excludedFiles: z.write(absPath, pathInBundle) + if pathInBundle not in buildVars.excludedFiles: + z.write(absPath, pathInBundle) + createAddonStoreJson(dest) return dest + +def createAddonStoreJson(bundle): + """Creates add-on store JSON file from an add-on package and manifest data.""" + import json + import hashlib + # Set different json file names and version number properties based on version number parsing results. + if env["versionNumber"] == "0.0.0": + env["versionNumber"] = buildVars.addon_info["addon_version"] + versionNumberParsed = env["versionNumber"].split(".") + if all([part.isnumeric() for part in versionNumberParsed]): + if len(versionNumberParsed) == 1: + versionNumberParsed += ["0", "0"] + elif len(versionNumberParsed) == 2: + versionNumberParsed.append("0") + else: + versionNumberParsed = [] + if len(versionNumberParsed): + major, minor, patch = [int(part) for part in versionNumberParsed] + jsonFilename = f'{major}.{minor}.{patch}.json' + else: + jsonFilename = f'{buildVars.addon_info["addon_version"]}.json' + major, minor, patch = 0, 0, 0 + print('Generating % s' % jsonFilename) + sha256 = hashlib.sha256() + with open(bundle, "rb") as f: + for byte_block in iter(lambda: f.read(65536), b""): + sha256.update(byte_block) + hashValue = sha256.hexdigest() + try: + minimumNVDAVersion = buildVars.addon_info["addon_minimumNVDAVersion"].split(".") + except AttributeError: + minimumNVDAVersion = [0, 0, 0] + minMajor, minMinor = minimumNVDAVersion[:2] + minPatch = minimumNVDAVersion[-1] if len(minimumNVDAVersion) == 3 else "0" + try: + lastTestedNVDAVersion = buildVars.addon_info["addon_lastTestedNVDAVersion"].split(".") + except AttributeError: + lastTestedNVDAVersion = [0, 0, 0] + lastTestedMajor, lastTestedMinor = lastTestedNVDAVersion[:2] + lastTestedPatch = lastTestedNVDAVersion[-1] if len(lastTestedNVDAVersion) == 3 else "0" + channel = buildVars.addon_info["addon_updateChannel"] + if channel is None: + channel = "stable" + addonStoreEntry = { + "addonId": buildVars.addon_info["addon_name"], + "displayName": buildVars.addon_info["addon_summary"], + "URL": "", + "description": buildVars.addon_info["addon_description"], + "sha256": hashValue, + "homepage": buildVars.addon_info["addon_url"], + "addonVersionName": buildVars.addon_info["addon_version"], + "addonVersionNumber": { + "major": major, + "minor": minor, + "patch": patch + }, + "minNVDAVersion": { + "major": int(minMajor), + "minor": int(minMinor), + "patch": int(minPatch) + }, + "lastTestedVersion": { + "major": int(lastTestedMajor), + "minor": int(lastTestedMinor), + "patch": int(lastTestedPatch) + }, + "channel": channel, + "publisher": "", + "sourceURL": buildVars.addon_info["addon_sourceURL"], + "license": buildVars.addon_info["addon_license"], + "licenseURL": buildVars.addon_info["addon_licenseURL"], + } + with open(jsonFilename, "w") as addonStoreJson: + json.dump(addonStoreEntry, addonStoreJson, indent="\t") + + def generateManifest(source, dest): addon_info = buildVars.addon_info - addon_info["addon_version"] = env["addon_version"] - addon_info["addon_updateChannel"] = env["addon_updateChannel"] with codecs.open(source, "r", "utf-8") as f: manifest_template = f.read() manifest = manifest_template.format(**addon_info) with codecs.open(dest, "w", "utf-8") as f: f.write(manifest) + def generateTranslatedManifest(source, language, out): _ = gettext.translation("nvda", localedir=os.path.join("addon", "locale"), languages=[language]).gettext vars = {} @@ -136,19 +269,24 @@ def generateTranslatedManifest(source, language, out): with codecs.open(out, "w", "utf-8") as f: f.write(result) + def expandGlobs(files): return [f for pattern in files for f in env.Glob(pattern)] + addon = env.NVDAAddon(addonFile, env.Dir('addon')) langDirs = [f for f in env.Glob(os.path.join("addon", "locale", "*"))] -#Allow all NVDA's gettext po files to be compiled in source/locale, and manifest files to be generated +# Allow all NVDA's gettext po files to be compiled in source/locale, and manifest files to be generated for dir in langDirs: poFile = dir.File(os.path.join("LC_MESSAGES", "nvda.po")) - moFile=env.gettextMoFile(poFile) + moFile = env.gettextMoFile(poFile) env.Depends(moFile, poFile) - translatedManifest = env.NVDATranslatedManifest(dir.File("manifest.ini"), [moFile, os.path.join("manifest-translated.ini.tpl")]) + translatedManifest = env.NVDATranslatedManifest( + dir.File("manifest.ini"), + [moFile, os.path.join("manifest-translated.ini.tpl")] + ) env.Depends(translatedManifest, ["buildVars.py"]) env.Depends(addon, [translatedManifest, moFile]) @@ -156,8 +294,9 @@ pythonFiles = expandGlobs(buildVars.pythonSources) for file in pythonFiles: env.Depends(addon, file) -#Convert markdown files to html -createAddonHelp("addon") # We need at least doc in English and should enable the Help button for the add-on in Add-ons Manager +# Convert markdown files to html +# We need at least doc in English and should enable the Help button for the add-on in Add-ons Manager +createAddonHelp("addon") for mdFile in env.Glob(os.path.join('addon', 'doc', '*', '*.md')): htmlFile = env.markdown(mdFile) env.Depends(htmlFile, mdFile) @@ -165,11 +304,11 @@ for mdFile in env.Glob(os.path.join('addon', 'doc', '*', '*.md')): # Pot target i18nFiles = expandGlobs(buildVars.i18nSources) -gettextvars={ - 'gettext_package_bugs_address' : 'nvda-translations@freelists.org', - 'gettext_package_name' : buildVars.addon_info['addon_name'], - 'gettext_package_version' : buildVars.addon_info['addon_version'] - } +gettextvars = { + 'gettext_package_bugs_address': 'nvda-translations@groups.io', + 'gettext_package_name': buildVars.addon_info['addon_name'], + 'gettext_package_version': buildVars.addon_info['addon_version'] +} pot = env.gettextPotFile("${addon_name}.pot", i18nFiles, **gettextvars) env.Alias('pot', pot) @@ -185,3 +324,4 @@ env.Depends(manifest, "buildVars.py") env.Depends(addon, manifest) env.Default(addon) +env.Clean(addon, ['.sconsign.dblite', 'addon/doc/' + buildVars.baseLanguage + '/']) diff --git a/updateVersion.py b/updateVersion.py new file mode 100644 index 0000000..c53e16f --- /dev/null +++ b/updateVersion.py @@ -0,0 +1,13 @@ +import re, sys + +if len(sys.argv) < 2: + print("the version was not detected") + exit(1) +version = sys.argv[1] +print(f"the version recognized is: {version}") +with open("buildVars.py", 'r+', encoding='utf-8') as f: + text = f.read() + text = re.sub('"addon_version" *:.*,', f'"addon_version" : "{version}",', text) + f.seek(0) + f.write(text) + f.truncate()