MultiLang

Форк
0
/
sconstruct 
327 строк · 11.8 Кб
1
# NVDA add-on template  SCONSTRUCT file
2
# Copyright (C) 2012-2023 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
def validateVersionNumber(key, val, env):
79
	# Used to make sure version major.minor.patch are integers to comply with NV Access add-on store.
80
	# Ignore all this if version number is not specified, in which case json generator will validate this info.
81
	if val == "0.0.0":
82
		return
83
	versionNumber = val.split(".")
84
	if len(versionNumber) < 3:
85
		raise ValueError("versionNumber must have three parts (major.minor.patch)")
86
	if not all([part.isnumeric() for part in versionNumber]):
87
		raise ValueError("versionNumber (major.minor.patch) must be integers")
88

89

90
vars = Variables()
91
vars.Add("version", "The version of this build", buildVars.addon_info["addon_version"])
92
vars.Add("versionNumber", "Version number of the form major.minor.patch", "0.0.0", validateVersionNumber)
93
vars.Add(BoolVariable("dev", "Whether this is a daily development version", False))
94
vars.Add("channel", "Update channel for this build", buildVars.addon_info["addon_updateChannel"])
95

96
env = Environment(variables=vars, ENV=os.environ, tools=['gettexttool', mdTool])
97
env.Append(**buildVars.addon_info)
98

99
if env["dev"]:
100
	import datetime
101
	buildDate = datetime.datetime.now()
102
	year, month, day = str(buildDate.year), str(buildDate.month), str(buildDate.day)
103
	versionTimestamp = "".join([year, month.zfill(2), day.zfill(2)])
104
	env["addon_version"] = f"{versionTimestamp}.0.0"
105
	env["versionNumber"] = f"{versionTimestamp}.0.0"
106
	env["channel"] = "dev"
107
elif env["version"] is not None:
108
	env["addon_version"] = env["version"]
109
if "channel" in env and env["channel"] is not None:
110
	env["addon_updateChannel"] = env["channel"]
111

112
buildVars.addon_info["addon_version"] = env["addon_version"]
113
buildVars.addon_info["addon_updateChannel"] = env["addon_updateChannel"]
114

115
addonFile = env.File("${addon_name}-${addon_version}.nvda-addon")
116

117

118
def addonGenerator(target, source, env, for_signature):
119
	action = env.Action(
120
		lambda target, source, env: createAddonBundleFromPath(source[0].abspath, target[0].abspath) and None,
121
		lambda target, source, env: "Generating Addon %s" % target[0]
122
	)
123
	return action
124

125

126
def manifestGenerator(target, source, env, for_signature):
127
	action = env.Action(
128
		lambda target, source, env: generateManifest(source[0].abspath, target[0].abspath) and None,
129
		lambda target, source, env: "Generating manifest %s" % target[0]
130
	)
131
	return action
132

133

134
def translatedManifestGenerator(target, source, env, for_signature):
135
	dir = os.path.abspath(os.path.join(os.path.dirname(str(source[0])), ".."))
136
	lang = os.path.basename(dir)
137
	action = env.Action(
138
		lambda target, source, env: generateTranslatedManifest(source[1].abspath, lang, target[0].abspath) and None,
139
		lambda target, source, env: "Generating translated manifest %s" % target[0]
140
	)
141
	return action
142

143

144
env['BUILDERS']['NVDAAddon'] = Builder(generator=addonGenerator)
145
env['BUILDERS']['NVDAManifest'] = Builder(generator=manifestGenerator)
146
env['BUILDERS']['NVDATranslatedManifest'] = Builder(generator=translatedManifestGenerator)
147

148

149
def createAddonHelp(dir):
150
	docsDir = os.path.join(dir, "doc")
151
	if os.path.isfile("style.css"):
152
		cssPath = os.path.join(docsDir, "style.css")
153
		cssTarget = env.Command(cssPath, "style.css", Copy("$TARGET", "$SOURCE"))
154
		env.Depends(addon, cssTarget)
155
	if os.path.isfile("readme.md"):
156
		readmePath = os.path.join(docsDir, buildVars.baseLanguage, "readme.md")
157
		readmeTarget = env.Command(readmePath, "readme.md", Copy("$TARGET", "$SOURCE"))
158
		env.Depends(addon, readmeTarget)
159

160

161
def createAddonBundleFromPath(path, dest):
162
	""" Creates a bundle from a directory that contains an addon manifest file."""
163
	basedir = os.path.abspath(path)
164
	with zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED) as z:
165
		# FIXME: the include/exclude feature may or may not be useful. Also python files can be pre-compiled.
166
		for dir, dirnames, filenames in os.walk(basedir):
167
			relativePath = os.path.relpath(dir, basedir)
168
			for filename in filenames:
169
				pathInBundle = os.path.join(relativePath, filename)
170
				absPath = os.path.join(dir, filename)
171
				if pathInBundle not in buildVars.excludedFiles:
172
					z.write(absPath, pathInBundle)
173
	createAddonStoreJson(dest)
174
	return dest
175

176

177
def createAddonStoreJson(bundle):
178
	"""Creates add-on store JSON file from an add-on package and manifest data."""
179
	import json
180
	import hashlib
181
	# Set different json file names and version number properties based on version number parsing results.
182
	if env["versionNumber"] == "0.0.0":
183
		env["versionNumber"] = buildVars.addon_info["addon_version"]
184
	versionNumberParsed = env["versionNumber"].split(".")
185
	if all([part.isnumeric() for part in versionNumberParsed]):
186
		if len(versionNumberParsed) == 1:
187
			versionNumberParsed += ["0", "0"]
188
		elif len(versionNumberParsed) == 2:
189
			versionNumberParsed.append("0")
190
	else:
191
		versionNumberParsed = []
192
	if len(versionNumberParsed):
193
		major, minor, patch = [int(part) for part in versionNumberParsed]
194
		jsonFilename = f'{major}.{minor}.{patch}.json'
195
	else:
196
		jsonFilename = f'{buildVars.addon_info["addon_version"]}.json'
197
		major, minor, patch = 0, 0, 0
198
	print('Generating % s' % jsonFilename)
199
	sha256 = hashlib.sha256()
200
	with open(bundle, "rb") as f:
201
		for byte_block in iter(lambda: f.read(65536), b""):
202
			sha256.update(byte_block)
203
	hashValue = sha256.hexdigest()
204
	try:
205
		minimumNVDAVersion = buildVars.addon_info["addon_minimumNVDAVersion"].split(".")
206
	except AttributeError:
207
		minimumNVDAVersion = [0, 0, 0]
208
	minMajor, minMinor = minimumNVDAVersion[:2]
209
	minPatch = minimumNVDAVersion[-1] if len(minimumNVDAVersion) == 3 else "0"
210
	try:
211
		lastTestedNVDAVersion = buildVars.addon_info["addon_lastTestedNVDAVersion"].split(".")
212
	except AttributeError:
213
		lastTestedNVDAVersion = [0, 0, 0]
214
	lastTestedMajor, lastTestedMinor = lastTestedNVDAVersion[:2]
215
	lastTestedPatch = lastTestedNVDAVersion[-1] if len(lastTestedNVDAVersion) == 3 else "0"
216
	channel = buildVars.addon_info["addon_updateChannel"]
217
	if channel is None:
218
		channel = "stable"
219
	addonStoreEntry = {
220
		"addonId": buildVars.addon_info["addon_name"],
221
		"displayName": buildVars.addon_info["addon_summary"],
222
		"URL": "",
223
		"description": buildVars.addon_info["addon_description"],
224
		"sha256": hashValue,
225
		"homepage": buildVars.addon_info["addon_url"],
226
		"addonVersionName": buildVars.addon_info["addon_version"],
227
		"addonVersionNumber": {
228
			"major": major,
229
			"minor": minor,
230
			"patch": patch
231
		},
232
		"minNVDAVersion": {
233
			"major": int(minMajor),
234
			"minor": int(minMinor),
235
			"patch": int(minPatch)
236
		},
237
		"lastTestedVersion": {
238
			"major": int(lastTestedMajor),
239
			"minor": int(lastTestedMinor),
240
			"patch": int(lastTestedPatch)
241
		},
242
		"channel": channel,
243
		"publisher": "",
244
		"sourceURL": buildVars.addon_info["addon_sourceURL"],
245
		"license": buildVars.addon_info["addon_license"],
246
		"licenseURL": buildVars.addon_info["addon_licenseURL"],
247
	}
248
	with open(jsonFilename, "w") as addonStoreJson:
249
		json.dump(addonStoreEntry, addonStoreJson, indent="\t")
250

251

252
def generateManifest(source, dest):
253
	addon_info = buildVars.addon_info
254
	with codecs.open(source, "r", "utf-8") as f:
255
		manifest_template = f.read()
256
	manifest = manifest_template.format(**addon_info)
257
	with codecs.open(dest, "w", "utf-8") as f:
258
		f.write(manifest)
259

260

261
def generateTranslatedManifest(source, language, out):
262
	_ = gettext.translation("nvda", localedir=os.path.join("addon", "locale"), languages=[language]).gettext
263
	vars = {}
264
	for var in ("addon_summary", "addon_description"):
265
		vars[var] = _(buildVars.addon_info[var])
266
	with codecs.open(source, "r", "utf-8") as f:
267
		manifest_template = f.read()
268
	result = manifest_template.format(**vars)
269
	with codecs.open(out, "w", "utf-8") as f:
270
		f.write(result)
271

272

273
def expandGlobs(files):
274
	return [f for pattern in files for f in env.Glob(pattern)]
275

276

277
addon = env.NVDAAddon(addonFile, env.Dir('addon'))
278

279
langDirs = [f for f in env.Glob(os.path.join("addon", "locale", "*"))]
280

281
# Allow all NVDA's gettext po files to be compiled in source/locale, and manifest files to be generated
282
for dir in langDirs:
283
	poFile = dir.File(os.path.join("LC_MESSAGES", "nvda.po"))
284
	moFile = env.gettextMoFile(poFile)
285
	env.Depends(moFile, poFile)
286
	translatedManifest = env.NVDATranslatedManifest(
287
		dir.File("manifest.ini"),
288
		[moFile, os.path.join("manifest-translated.ini.tpl")]
289
	)
290
	env.Depends(translatedManifest, ["buildVars.py"])
291
	env.Depends(addon, [translatedManifest, moFile])
292

293
pythonFiles = expandGlobs(buildVars.pythonSources)
294
for file in pythonFiles:
295
	env.Depends(addon, file)
296

297
# Convert markdown files to html
298
# We need at least doc in English and should enable the Help button for the add-on in Add-ons Manager
299
createAddonHelp("addon")
300
for mdFile in env.Glob(os.path.join('addon', 'doc', '*', '*.md')):
301
	htmlFile = env.markdown(mdFile)
302
	env.Depends(htmlFile, mdFile)
303
	env.Depends(addon, htmlFile)
304

305
# Pot target
306
i18nFiles = expandGlobs(buildVars.i18nSources)
307
gettextvars = {
308
	'gettext_package_bugs_address': 'nvda-translations@groups.io',
309
	'gettext_package_name': buildVars.addon_info['addon_name'],
310
	'gettext_package_version': buildVars.addon_info['addon_version']
311
}
312

313
pot = env.gettextPotFile("${addon_name}.pot", i18nFiles, **gettextvars)
314
env.Alias('pot', pot)
315
env.Depends(pot, i18nFiles)
316
mergePot = env.gettextMergePotFile("${addon_name}-merge.pot", i18nFiles, **gettextvars)
317
env.Alias('mergePot', mergePot)
318
env.Depends(mergePot, i18nFiles)
319

320
# Generate Manifest path
321
manifest = env.NVDAManifest(os.path.join("addon", "manifest.ini"), os.path.join("manifest.ini.tpl"))
322
# Ensure manifest is rebuilt if buildVars is updated.
323
env.Depends(manifest, "buildVars.py")
324

325
env.Depends(addon, manifest)
326
env.Default(addon)
327
env.Clean(addon, ['.sconsign.dblite', 'addon/doc/' + buildVars.baseLanguage + '/'])
328

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.