Skip to content

Commit 74b950b

Browse files
committed
Initial commit for NVDA plugin
1 parent 25c7883 commit 74b950b

File tree

11 files changed

+994
-0
lines changed

11 files changed

+994
-0
lines changed

NVDA-addon/.gitattributes

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Set default behaviour, in case users don't have core.autocrlf set.
2+
* text=auto
3+
4+
# Try to ensure that po files in the repo does not include
5+
# source code line numbers.
6+
# Every person expected to commit po files should change their personal config file as described here:
7+
# https://mail.gnome.org/archives/kupfer-list/2010-June/msg00002.html
8+
*.po filter=cleanpo

NVDA-addon/.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
addon/doc/*.css
2+
addon/doc/en/
3+
*_docHandler.py
4+
*.html
5+
*.ini
6+
*.mo
7+
*.pot
8+
*.py[co]
9+
*.nvda-addon
10+
.sconsign.dblite

NVDA-addon/README

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
To build the addon, type "scons".
2+
This rebuilds addon/manifest.ini and then zips up the addon dir to create
3+
mathCAT-0.1.nvda-addon (where the version number comes from the manifest)

NVDA-addon/README.install

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
To install this addon for NVDA:
2+
1. Open the NVDA menu
3+
2. Choose Tools
4+
3. Choose Manage add-ons...
5+
4. Click Install...
6+
5. Pick the location where you saved mathCAT (the file name starts with "mathCAT" and ends with ".nvda-addon". The version number in the middle will change)
7+
6. Click "Open"
8+
7. Click "Yes" to agree you want to install it.
9+
8. Click "Close" to restart NVDA with MathCAT enabled.
10+

NVDA-addon/build.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/csh
2+
3+
cargo build --release
4+
5+
# copy over all the rules and then remove a few "extra" files
6+
cp -r ../../MathCAT/Rules addon/globalPlugins/
7+
rm -rf addon/globalPlugins/Rules/.tmp.driveupload
8+
rm -f addon/globalPlugins/Rules/Nemeth/unicode.yaml-with-all
9+
rm -rf addon/globalPlugins/Rules/zz
10+
11+
cp ../target/i686-pc-windows-msvc/release/libmathcat_py.dll addon/globalPlugins/libmathcat.pyd
12+
rm mathCAT-0.1.1.nvda-addon
13+
scons
14+

NVDA-addon/buildVars.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# -*- coding: UTF-8 -*-
2+
3+
# Build customizations
4+
# Change this file instead of sconstruct or manifest files, whenever possible.
5+
6+
7+
# Since some strings in `addon_info` are translatable,
8+
# we need to include them in the .po files.
9+
# Gettext recognizes only strings given as parameters to the `_` function.
10+
# To avoid initializing translations in this module we simply roll our own "fake" `_` function
11+
# which returns whatever is given to it as an argument.
12+
def _(arg):
13+
return arg
14+
15+
16+
# Add-on information variables
17+
addon_info = {
18+
# add-on Name/identifier, internal for NVDA
19+
"addon_name": "mathCAT",
20+
# Add-on summary, usually the user visible name of the addon.
21+
# Translators: Summary for this add-on
22+
# to be shown on installation and add-on information found in Add-ons Manager.
23+
"addon_summary": _("MathCAT: speech and braille to MathML"),
24+
# Add-on description
25+
# Translators: Long description to be shown for this add-on on add-on information from add-ons manager
26+
"addon_description": _("""
27+
MathCAT is a replacement for MathPlayer which has been discontinued.
28+
It provides speech and braille support, and also supports MathPlayer's three modes of navigation.
29+
The initial version of MathCAT is English-only but is designed with translations in mind.
30+
"""),
31+
# version
32+
"addon_version": "0.1.1",
33+
# Author(s)
34+
"addon_author": "Neil Soiffer <soiffer@alum.mit.edu>",
35+
# URL for the add-on documentation support
36+
"addon_url": "https://nsoiffer.github.io/MathCAT/",
37+
# Documentation file name
38+
"addon_docFileName": "readme.html",
39+
# Minimum NVDA version supported (e.g. "2018.3.0", minor version is optional)
40+
"addon_minimumNVDAVersion": None,
41+
# Last NVDA version supported/tested (e.g. "2018.4.0", ideally more recent than minimum version)
42+
"addon_lastTestedNVDAVersion": "2021.3",
43+
# Add-on update channel (default is None, denoting stable releases,
44+
# and for development releases, use "dev".)
45+
# Do not change unless you know what you are doing!
46+
"addon_updateChannel": "dev",
47+
}
48+
49+
# Define the python files that are the sources of your add-on.
50+
# You can either list every file (using ""/") as a path separator,
51+
# or use glob expressions.
52+
# For example to include all files with a ".py" extension from the "globalPlugins" dir of your add-on
53+
# the list can be written as follows:
54+
# pythonSources = ["addon/globalPlugins/*.py"]
55+
# For more information on SCons Glob expressions please take a look at:
56+
# https://scons.org/doc/production/HTML/scons-user/apd.html
57+
pythonSources = ["addon/globalPlugins/mathcat.py"]
58+
59+
# Files that contain strings for translation. Usually your python sources
60+
i18nSources = pythonSources + ["buildVars.py"]
61+
62+
# Files that will be ignored when building the nvda-addon file
63+
# Paths are relative to the addon directory, not to the root directory of your addon sources.
64+
excludedFiles = []
65+
66+
# Base language for the NVDA add-on
67+
# If your add-on is written in a language other than english, modify this variable.
68+
# For example, set baseLanguage to "es" if your add-on is primarily written in spanish.
69+
baseLanguage = "en"
70+
71+
# Markdown extensions for add-on documentation
72+
# Most add-ons do not require additional Markdown extensions.
73+
# If you need to add support for markup such as tables, fill out the below list.
74+
# Extensions string must be of the form "markdown.extensions.extensionName"
75+
# e.g. "markdown.extensions.tables" to add tables.
76+
markdownExtensions = []
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
summary = "{addon_summary}"
2+
description = """{addon_description}"""

NVDA-addon/manifest.ini.tpl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name = {addon_name}
2+
summary = "{addon_summary}"
3+
description = """{addon_description}"""
4+
author = "{addon_author}"
5+
url = {addon_url}
6+
version = {addon_version}
7+
docFileName = {addon_docFileName}
8+
minimumNVDAVersion = {addon_minimumNVDAVersion}
9+
lastTestedNVDAVersion = {addon_lastTestedNVDAVersion}
10+
updateChannel = {addon_updateChannel}

NVDA-addon/sconstruct

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# NVDA add-on template SCONSTRUCT file
2+
# Copyright (C) 2012-2021 Rui Batista, Noelia Martinez, Joseph Lee
3+
# This file is covered by the GNU General Public License.
4+
# See the file COPYING.txt for more details.
5+
6+
import codecs
7+
import gettext
8+
import os
9+
import os.path
10+
import zipfile
11+
import sys
12+
13+
# While names imported below are available by default in every SConscript
14+
# Linters aren't aware about them.
15+
# To avoid Flake8 F821 warnings about them they are imported explicitly.
16+
# When using other Scons functions please add them to the line below.
17+
from SCons.Script import BoolVariable, Builder, Copy, Environment, Variables
18+
19+
sys.dont_write_bytecode = True
20+
21+
# Bytecode should not be written for build vars module to keep the repository root folder clean.
22+
import buildVars # NOQA: E402
23+
24+
25+
def md2html(source, dest):
26+
import markdown
27+
# Use extensions if defined.
28+
mdExtensions = buildVars.markdownExtensions
29+
lang = os.path.basename(os.path.dirname(source)).replace('_', '-')
30+
localeLang = os.path.basename(os.path.dirname(source))
31+
try:
32+
_ = gettext.translation("nvda", localedir=os.path.join("addon", "locale"), languages=[localeLang]).gettext
33+
summary = _(buildVars.addon_info["addon_summary"])
34+
except Exception:
35+
summary = buildVars.addon_info["addon_summary"]
36+
title = "{addonSummary} {addonVersion}".format(
37+
addonSummary=summary, addonVersion=buildVars.addon_info["addon_version"]
38+
)
39+
headerDic = {
40+
"[[!meta title=\"": "# ",
41+
"\"]]": " #",
42+
}
43+
with codecs.open(source, "r", "utf-8") as f:
44+
mdText = f.read()
45+
for k, v in headerDic.items():
46+
mdText = mdText.replace(k, v, 1)
47+
htmlText = markdown.markdown(mdText, extensions=mdExtensions)
48+
# Optimization: build resulting HTML text in one go instead of writing parts separately.
49+
docText = "\n".join([
50+
"<!DOCTYPE html>",
51+
"<html lang=\"%s\">" % lang,
52+
"<head>",
53+
"<meta charset=\"UTF-8\">"
54+
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
55+
"<link rel=\"stylesheet\" type=\"text/css\" href=\"../style.css\" media=\"screen\">",
56+
"<title>%s</title>" % title,
57+
"</head>\n<body>",
58+
htmlText,
59+
"</body>\n</html>"
60+
])
61+
with codecs.open(dest, "w", "utf-8") as f:
62+
f.write(docText)
63+
64+
65+
def mdTool(env):
66+
mdAction = env.Action(
67+
lambda target, source, env: md2html(source[0].path, target[0].path),
68+
lambda target, source, env: 'Generating % s' % target[0],
69+
)
70+
mdBuilder = env.Builder(
71+
action=mdAction,
72+
suffix='.html',
73+
src_suffix='.md',
74+
)
75+
env['BUILDERS']['markdown'] = mdBuilder
76+
77+
78+
vars = Variables()
79+
vars.Add("version", "The version of this build", buildVars.addon_info["addon_version"])
80+
vars.Add(BoolVariable("dev", "Whether this is a daily development version", False))
81+
vars.Add("channel", "Update channel for this build", buildVars.addon_info["addon_updateChannel"])
82+
83+
env = Environment(variables=vars, ENV=os.environ, tools=['gettexttool', mdTool])
84+
env.Append(**buildVars.addon_info)
85+
86+
if env["dev"]:
87+
import datetime
88+
buildDate = datetime.datetime.now()
89+
year, month, day = str(buildDate.year), str(buildDate.month), str(buildDate.day)
90+
env["addon_version"] = "".join([year, month.zfill(2), day.zfill(2), "-dev"])
91+
env["channel"] = "dev"
92+
elif env["version"] is not None:
93+
env["addon_version"] = env["version"]
94+
if "channel" in env and env["channel"] is not None:
95+
env["addon_updateChannel"] = env["channel"]
96+
97+
buildVars.addon_info["addon_version"] = env["addon_version"]
98+
buildVars.addon_info["addon_updateChannel"] = env["addon_updateChannel"]
99+
100+
addonFile = env.File("${addon_name}-${addon_version}.nvda-addon")
101+
102+
103+
def addonGenerator(target, source, env, for_signature):
104+
action = env.Action(
105+
lambda target, source, env: createAddonBundleFromPath(source[0].abspath, target[0].abspath) and None,
106+
lambda target, source, env: "Generating Addon %s" % target[0]
107+
)
108+
return action
109+
110+
111+
def manifestGenerator(target, source, env, for_signature):
112+
action = env.Action(
113+
lambda target, source, env: generateManifest(source[0].abspath, target[0].abspath) and None,
114+
lambda target, source, env: "Generating manifest %s" % target[0]
115+
)
116+
return action
117+
118+
119+
def translatedManifestGenerator(target, source, env, for_signature):
120+
dir = os.path.abspath(os.path.join(os.path.dirname(str(source[0])), ".."))
121+
lang = os.path.basename(dir)
122+
action = env.Action(
123+
lambda target, source, env: generateTranslatedManifest(source[1].abspath, lang, target[0].abspath) and None,
124+
lambda target, source, env: "Generating translated manifest %s" % target[0]
125+
)
126+
return action
127+
128+
129+
env['BUILDERS']['NVDAAddon'] = Builder(generator=addonGenerator)
130+
env['BUILDERS']['NVDAManifest'] = Builder(generator=manifestGenerator)
131+
env['BUILDERS']['NVDATranslatedManifest'] = Builder(generator=translatedManifestGenerator)
132+
133+
134+
def createAddonHelp(dir):
135+
docsDir = os.path.join(dir, "doc")
136+
if os.path.isfile("style.css"):
137+
cssPath = os.path.join(docsDir, "style.css")
138+
cssTarget = env.Command(cssPath, "style.css", Copy("$TARGET", "$SOURCE"))
139+
env.Depends(addon, cssTarget)
140+
if os.path.isfile("readme.md"):
141+
readmePath = os.path.join(docsDir, buildVars.baseLanguage, "readme.md")
142+
readmeTarget = env.Command(readmePath, "readme.md", Copy("$TARGET", "$SOURCE"))
143+
env.Depends(addon, readmeTarget)
144+
145+
146+
def createAddonBundleFromPath(path, dest):
147+
""" Creates a bundle from a directory that contains an addon manifest file."""
148+
basedir = os.path.abspath(path)
149+
with zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED) as z:
150+
# FIXME: the include/exclude feature may or may not be useful. Also python files can be pre-compiled.
151+
for dir, dirnames, filenames in os.walk(basedir):
152+
relativePath = os.path.relpath(dir, basedir)
153+
for filename in filenames:
154+
pathInBundle = os.path.join(relativePath, filename)
155+
absPath = os.path.join(dir, filename)
156+
if pathInBundle not in buildVars.excludedFiles:
157+
z.write(absPath, pathInBundle)
158+
return dest
159+
160+
161+
def generateManifest(source, dest):
162+
addon_info = buildVars.addon_info
163+
with codecs.open(source, "r", "utf-8") as f:
164+
manifest_template = f.read()
165+
manifest = manifest_template.format(**addon_info)
166+
with codecs.open(dest, "w", "utf-8") as f:
167+
f.write(manifest)
168+
169+
170+
def generateTranslatedManifest(source, language, out):
171+
_ = gettext.translation("nvda", localedir=os.path.join("addon", "locale"), languages=[language]).gettext
172+
vars = {}
173+
for var in ("addon_summary", "addon_description"):
174+
vars[var] = _(buildVars.addon_info[var])
175+
with codecs.open(source, "r", "utf-8") as f:
176+
manifest_template = f.read()
177+
result = manifest_template.format(**vars)
178+
with codecs.open(out, "w", "utf-8") as f:
179+
f.write(result)
180+
181+
182+
def expandGlobs(files):
183+
return [f for pattern in files for f in env.Glob(pattern)]
184+
185+
186+
addon = env.NVDAAddon(addonFile, env.Dir('addon'))
187+
188+
langDirs = [f for f in env.Glob(os.path.join("addon", "locale", "*"))]
189+
190+
# Allow all NVDA's gettext po files to be compiled in source/locale, and manifest files to be generated
191+
for dir in langDirs:
192+
poFile = dir.File(os.path.join("LC_MESSAGES", "nvda.po"))
193+
moFile = env.gettextMoFile(poFile)
194+
env.Depends(moFile, poFile)
195+
translatedManifest = env.NVDATranslatedManifest(
196+
dir.File("manifest.ini"),
197+
[moFile, os.path.join("manifest-translated.ini.tpl")]
198+
)
199+
env.Depends(translatedManifest, ["buildVars.py"])
200+
env.Depends(addon, [translatedManifest, moFile])
201+
202+
pythonFiles = expandGlobs(buildVars.pythonSources)
203+
for file in pythonFiles:
204+
env.Depends(addon, file)
205+
206+
# Convert markdown files to html
207+
# We need at least doc in English and should enable the Help button for the add-on in Add-ons Manager
208+
createAddonHelp("addon")
209+
for mdFile in env.Glob(os.path.join('addon', 'doc', '*', '*.md')):
210+
htmlFile = env.markdown(mdFile)
211+
env.Depends(htmlFile, mdFile)
212+
env.Depends(addon, htmlFile)
213+
214+
# Pot target
215+
i18nFiles = expandGlobs(buildVars.i18nSources)
216+
gettextvars = {
217+
'gettext_package_bugs_address': 'nvda-translations@groups.io',
218+
'gettext_package_name': buildVars.addon_info['addon_name'],
219+
'gettext_package_version': buildVars.addon_info['addon_version']
220+
}
221+
222+
pot = env.gettextPotFile("${addon_name}.pot", i18nFiles, **gettextvars)
223+
env.Alias('pot', pot)
224+
env.Depends(pot, i18nFiles)
225+
mergePot = env.gettextMergePotFile("${addon_name}-merge.pot", i18nFiles, **gettextvars)
226+
env.Alias('mergePot', mergePot)
227+
env.Depends(mergePot, i18nFiles)
228+
229+
# Generate Manifest path
230+
manifest = env.NVDAManifest(os.path.join("addon", "manifest.ini"), os.path.join("manifest.ini.tpl"))
231+
# Ensure manifest is rebuilt if buildVars is updated.
232+
env.Depends(manifest, "buildVars.py")
233+
234+
env.Depends(addon, manifest)
235+
env.Default(addon)
236+
env.Clean(addon, ['.sconsign.dblite', 'addon/doc/' + buildVars.baseLanguage + '/'])

0 commit comments

Comments
 (0)