├── src ├── updateVersion.py ├── post-commit ├── typeString.py └── _configHelper.py ├── NVDADevelopmentUtilities.code-workspace ├── readme.md └── spanishReadme.md /src/updateVersion.py: -------------------------------------------------------------------------------- 1 | import re, sys 2 | 3 | if len(sys.argv) < 2: 4 | print("the version was not detected") 5 | exit(1) 6 | version = sys.argv[1] 7 | print(f"the version recognized is: {version}") 8 | with open("buildVars.py", 'r+', encoding='utf-8') as f: 9 | text = f.read() 10 | text = re.sub('"addon_version" *:.*,', f'"addon_version" : "{version}",', text) 11 | f.seek(0) 12 | f.write(text) 13 | f.truncate() 14 | -------------------------------------------------------------------------------- /src/post-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | commit_msg=$(git log -1 --pretty=%B) 4 | if [[ "$commit_msg" =~ ^\s*version\ ([0-9]+(\.[0-9]+){2}) ]] && ! $(grep -q "\"addon_version\"\s*:\s*\"${BASH_REMATCH[1]}\"" buildVars.py); then 5 | version_number="${BASH_REMATCH[1]}" 6 | echo "A version instruction was detected in the comit message distinct to the version of buildVars.py. The file will be updated with the version detected: $version_number" 7 | python updateVersion.py "$version_number" 8 | wait 9 | git add buildVars.py 10 | git commit --amend --no-edit 11 | wait 12 | git tag "$version_number" 13 | fi 14 | -------------------------------------------------------------------------------- /src/typeString.py: -------------------------------------------------------------------------------- 1 | # TypeString. 2 | # Copyright (C) 2022 David CM 3 | # this is a small piece of code to help you to write a string in a text input. 4 | 5 | import brailleInput, threading, winUser 6 | 7 | modifiers = [ 8 | winUser.VK_LCONTROL, 9 | winUser.VK_RCONTROL, 10 | winUser.VK_LSHIFT, 11 | winUser.VK_RSHIFT, 12 | winUser.VK_LMENU, 13 | winUser.VK_RMENU, 14 | winUser.VK_LWIN, 15 | winUser.VK_RWIN 16 | ] 17 | 18 | def typeString(s): 19 | """ this function types the specified string acting like an user typing. 20 | params: 21 | @s: the string to type. 22 | """ 23 | # first, release all modifiers to avoid issues when typing. 24 | for k in modifiers: 25 | if winUser.getKeyState(k) & 32768: 26 | winUser.keybd_event(k, 0, 2, 0) 27 | # now type the string. I used a timer, I didn't remember why. 28 | # but I'm sure that it was to solve an issue. 29 | threading.Timer(0.01, brailleInput.handler.sendChars, [s]).start() 30 | -------------------------------------------------------------------------------- /NVDADevelopmentUtilities.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "editor.accessibilitySupport": "on", 9 | "python.linting.enabled": true, 10 | "python.linting.mypyEnabled": true, 11 | "python.linting.mypyArgs": [ 12 | "--follow-imports=silent", 13 | "--ignore-missing-imports", 14 | "--show-column-numbers", 15 | "--disallow-untyped-defs", 16 | 17 | ], 18 | "python.autoComplete.extraPaths": [ 19 | "../../nvda/source", 20 | "../../nvda/include/comtypes", 21 | "../../nvda/include/configobj/src", 22 | "../../nvda/include/pyserial", 23 | "../../nvda/include/wxPython", 24 | "../../nvda/miscDeps/python" 25 | ], 26 | "files.insertFinalNewline": true, 27 | "files.trimFinalNewlines": true, 28 | "editor.insertSpaces": false, 29 | "python.testing.unittestArgs": [ 30 | "-v", 31 | "-s", 32 | "tests.unit", 33 | "-p", 34 | "test_*.py" 35 | ], 36 | "python.testing.pytestEnabled": false, 37 | "python.testing.nosetestsEnabled": false, 38 | "python.testing.unittestEnabled": false, 39 | "python.analysis.extraPaths": [ 40 | "addon", 41 | "../../nvda/source", 42 | "../../nvda/include/comtypes", 43 | "../../nvda/include/configobj/src", 44 | "../../nvda/include/pyserial", 45 | "../../nvda/include/wxPython", 46 | "../../nvda/miscDeps/python" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/_configHelper.py: -------------------------------------------------------------------------------- 1 | # NVDA configHelper. 2 | # Copyright (C) 2022 David CM 3 | 4 | import config 5 | 6 | def getDictObjFromPath(initObj, path): 7 | """ this function helps to get a value from nested dictionaries. 8 | params 9 | @initObj: the initial object. 10 | @path: a list with the path to get the final object. 11 | """ 12 | for k in path: 13 | initObj = initObj[k] 14 | return initObj 15 | 16 | def getConfigValue(path, optName, generalProfile=False): 17 | """ this function helps to accessing config values. 18 | params 19 | @path: the path to the option. 20 | @optName: the option name 21 | @generalProfile: if true, the general profile will be used, instead of the current profile. 22 | @returns: the current value, if Exists. Or an exception if the path is not valid. 23 | """ 24 | obj = config.conf.profiles[0] if generalProfile else config.conf 25 | return getDictObjFromPath(obj, path)[optName] 26 | 27 | 28 | def setConfigValue(path, optName, value, generalProfile=False): 29 | """ this function helps to accessing and set config values. 30 | params 31 | @path: the path to the option. 32 | @optName: the option name 33 | @value: the value to set. 34 | @generalProfile: if true, the general profile will be used, instead of the current profile. 35 | """ 36 | obj = config.conf.profiles[0] if generalProfile else config.conf 37 | getDictObjFromPath(obj, path)[optName] = value 38 | 39 | 40 | def boolValidator(val): 41 | if isinstance(val, bool): 42 | return val 43 | return eval(val) 44 | 45 | 46 | def registerGeneralOption(path, option, defaultValue): 47 | obj = config.conf.profiles[0] 48 | for k in path: 49 | if k not in obj: 50 | obj[k] = {} 51 | obj = obj[k] 52 | if (option not in obj): 53 | obj[option] = defaultValue 54 | 55 | 56 | def registerConfig(clsSpec, path=None): 57 | AF = clsSpec(path) 58 | specObj = getDictObjFromPath(config.conf.spec, AF.__path__[0:-1]) 59 | specObj[AF.__path__[-1]] = AF.__createSpec__() 60 | # for general profile options 61 | for k in clsSpec.__getConfigOpts__(): 62 | v = getattr(clsSpec, k) 63 | if isinstance(v, tuple) and v[1]: 64 | registerGeneralOption(AF.__path__, k, getattr(AF, k)) 65 | return AF 66 | 67 | fakeValidator = lambda x: x 68 | class OptConfig: 69 | """ just a helper descriptor to create the main class to accesing config values. 70 | the option name will be taken from the declared variable. 71 | """ 72 | def __init__(self, desc): 73 | """ 74 | params: 75 | @desc: the spec description. Can be a string (with the description of configobj) or a tuble with the configobj first, and the second value is a flag that if it's true, the option will be assigned to the default profile only. 76 | """ 77 | self.generalProfile = False 78 | self.validator = fakeValidator 79 | if isinstance(desc, tuple): 80 | self.generalProfile = desc[1] 81 | try: 82 | self.validator = desc[2] 83 | except: 84 | pass 85 | desc = desc[0] 86 | self.desc = desc 87 | 88 | def __set_name__(self, owner, name): 89 | self.name = name 90 | 91 | def __get__(self, obj, type=None): 92 | if obj: 93 | try: 94 | return self.validator(getConfigValue(obj.__path__, self.name, self.generalProfile)) 95 | except KeyError: 96 | return getConfigValue(obj.__path__, self.name) 97 | if self.generalProfile: 98 | return (self.desc, self.generalProfile) 99 | return self.desc 100 | 101 | def __set__(self, obj, value): 102 | setConfigValue(obj.__path__, self.name, value, self.generalProfile) 103 | 104 | 105 | class BaseConfig: 106 | """ this class will help to get and set config values. 107 | the idea behind this is to generalize the config path and config names. 108 | sometimes, a mistake in the dict to access the values can produce an undetectable bug. 109 | """ 110 | __path__ = None 111 | def __init__(self, path=None): 112 | if not path: 113 | path = self.__class__.__path__ 114 | if not path: 115 | raise Exception("Path for the config is not defined") 116 | if isinstance(path, list): 117 | self.__path__ = path 118 | else: 119 | self.__path__ = [path] 120 | 121 | @classmethod 122 | def __getConfigOpts__(cls, c=None): 123 | if c: cls = c 124 | return [k for k in cls.__dict__ if not k.startswith("__")] 125 | 126 | def __createSpec__(self): 127 | """ this method creates a config spec with the provided attributes in the class 128 | """ 129 | s = {} 130 | for k in self.__class__.__getConfigOpts__(): 131 | v = getattr(self.__class__, k) 132 | if isinstance(v, tuple): v = v[0] 133 | s[k] = v 134 | return s 135 | 136 | 137 | def configSpec(pathOrCls): 138 | """ a decorator to help with the generation of the class config spec. 139 | adds a get and set descriptor for eatch attribute in the config class. 140 | except the attributes starting with "__". 141 | params: 142 | @pathOrCls: the config path, 143 | or if the decorator is called without params, then the decorated class. 144 | path as an argument in the decorator has a higher priority than the __path__ declared in the class. 145 | """ 146 | def configDecorator(cls): 147 | class ConfigSpec(BaseConfig): 148 | pass 149 | for k in ConfigSpec.__getConfigOpts__(cls): 150 | v = getattr(cls, k) 151 | d = OptConfig(v) 152 | d.__set_name__(ConfigSpec, k) 153 | setattr(ConfigSpec, k, d) 154 | ConfigSpec.__path__ = path 155 | return ConfigSpec 156 | if isinstance(pathOrCls, str): 157 | path = pathOrCls 158 | return configDecorator 159 | else: 160 | path = pathOrCls.__path__ 161 | return configDecorator(pathOrCls) 162 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # NVDA development utilities. 2 | 3 | See the 4 | [spanish version here](/spanishReadme.md) 5 | 6 | This repo will contain all the utilities I've written or found in any place, to help with the development of NVDA's add-ons. 7 | 8 | ## config helper. 9 | [Get the utility here](https://raw.githubusercontent.com/davidacm/NVDADevelopmentUtilities/master/src/_configHelper.py) 10 | 11 | This is a small utility to deal with NVDA's settings in our add-ons. 12 | 13 | The philosophy here is that strings related to coding should only be written in a centralized place. 14 | Because IDE's usually can't help you to autocomplete inside a string, then you can make mistakes if you change just a letter. Remember that, for example a string as a key in a dict, is case sensitive. 15 | 16 | I really hate to write config.conf.['a1']['a2]['option'] each time I 17 | need to access, or set a value in the configuration. 18 | 19 | 20 | ### usage. 21 | 22 | This consist on a class for the specification and a very simple descriptor, to get and 23 | access the config values. Don't worry, you don't need to use the descriptor directly, the class decorator will do all the stuff for you. 24 | 25 | There are many ways to use it, but the simplest way is by using the class decorator. 26 | 27 | To access the configuration spec, access the property of the declared class. The instance will only access the current value of the setting and not the spec. 28 | 29 | Usually the specification can be written in a string. But if your configuration behaves a little differently than usual, you can use a tuple of values to change the behavior of the configurations. 30 | These are the currently supported values when you use tuples: 31 | 32 | 1. String specifying the option. 33 | 2. True to indicate that the settings should be generalized for all profiles, or false to indicate that they should be specific to each NVDA profile. Default is false. 34 | 3. (optional parameter) A type validation function. Since NVDA does not check or convert types when accessing the general profile directly, you can specify a function to automatically validate the option's value. This only makes sense if you set the second parameter to True. 35 | 36 | ### Code example. 37 | 38 | This example was taken from the [Beep Keyboard](https://github.com/davidacm/beepKeyboard) add-on. 39 | 40 | ``` 41 | # first, import the utility. The decorator and the register config function. 42 | 43 | from ._configHelper import configSpec, registerConfig 44 | 45 | # now the class definition, with the decorator first. 46 | # this decorator will replace the attributes with a descriptor to manage accessing and updating values. 47 | # you can specify the path config in the decorator or in the class. I prefer it in the decorator. 48 | # if you prefer to set it in the class, just add an attribute called __path__ inside the class, e.g. 49 | # __path__ = "..." 50 | # the path given in the decorator has a higher priority. 51 | 52 | @configSpec('beepKeyboard') 53 | class AppConfig: 54 | # the definition of the settings. in form of name = 'desc' 55 | beepUpperWithCapsLock = 'boolean(default=True)' 56 | beepCharacterWithShift = 'boolean(default=False)' 57 | beepToggleKeyChanges = 'boolean(default=False)' 58 | announceToggleStatus = 'boolean(default=True)' 59 | disableBeepingOnPasswordFields = 'boolean(default=True)' 60 | ignoredCharactersForShift = "string(default='\\x1b\\t\\b\\r ')" 61 | beepForCharacters = "string(default='')" 62 | shiftedCharactersTone = 'int_list(default=list(6000,10,25))' 63 | customCharactersTone = 'int_list(default=list(6000,10,25))' 64 | capsLockUpperTone = 'int_list(default=list(3000,40,50))' 65 | toggleOffTone = 'int_list(default=list(500,40,50))' 66 | toggleOnTone = 'int_list(default=list(2000, 40, 50))' 67 | AF = registerConfig(AppConfig) 68 | 69 | # accessing an option: 70 | print("this should print False", AF.disableBeepingOnPasswordFields) 71 | # changing the value: 72 | AF.disableBeepingOnPasswordFields = True 73 | # let's see the new value. 74 | print("this should print True", AF.disableBeepingOnPasswordFields) 75 | ``` 76 | 77 | Let's see an example with options for the general profile only. This was taken from [IBMTTS](https://github.com/davidacm/NVDA-IBMTTS-Driver) add-on. 78 | 79 | ``` 80 | from ._configHelper import configSpec, registerConfig, boolValidator 81 | @configSpec("ibmeci") 82 | class _AppConfig: 83 | dllName = ("string(default='eci.dll')", True) 84 | TTSPath = ("string(default='ibmtts')", True) 85 | # General profile option with a validator function for bools. 86 | autoUpdate = ('boolean(default=True)', True, boolValidator) 87 | appConfig = registerConfig(_AppConfig) 88 | ``` 89 | 90 | ### Real use cases: 91 | 92 | * [beepKeyboard](https://github.com/davidacm/beepkeyboard) 93 | * [SpeechHistoryExplorer](https://github.com/davidacm/SpeechHistoryExplorer) 94 | * [IBMTTS](https://github.com/davidacm/NVDA-IBMTTS-Driver) 95 | * [Wake Speaker](https://github.com/davidacm/WakeSpeaker) 96 | 97 | ## typeString. 98 | [Get the function code here](https://raw.githubusercontent.com/davidacm/NVDADevelopmentUtilities/master/src/typeString.py) 99 | 100 | This is a small piece of code to help you to write a string in a text input. Sometimes add-on developers copy a text in the clipboard, and then paste it. But in my opinion, that is not a good idea because it interferes with the clipboard of the user. 101 | 102 | I developed this function a long time ago. Currently I'm not using it, this might have some bugs. Use it, and if you encounter an issue and you can fix it, please let me know the solution. 103 | 104 | ### usage 105 | 106 | Just call the function with the string that you need to type. For example: 107 | 108 | typeString("Hello, this is a test") 109 | 110 | ## "updateVersion.py". Utility to update the version of buildVars.py 111 | 112 | This little script does not require any external modules. It's just a python file to update the version of the add-on in the "buildVars.py". Just pass the desired version, for example: 113 | "python updateVersion.py 2023.5.2" 114 | 115 | If the script recognizes an argument passed to the script, it will identify that as the version you want to assign. 116 | 117 | ## "post-commit". Hook to update the version if you forgot to do it. 118 | 119 | This idea came about because I always forget to update buildVars.py.. Updating the version of an add-on requires updating the same thing in several parts and I'm not good at repetitive tasks. 120 | I used to update the version in a github workflow that I use to automatically launch new releases, but I don't like the idea of ​​having buildVars out of sync with the latest version. 121 | 122 | So if you don't want to mess with all that, let a hook do it for you. 123 | In order to do so, the use of the "updateVersion.py" script is required. 124 | 125 | ### Usage. 126 | 1. Put the file "post-commit" inside .git/hooks folder. 127 | 2. If you are going to release a new version, write in the first line of the commit the version in this way: "version 1.2.3". The version must consist of three numbers, as it is a requirement for the NVDA Add-on Store. 128 | 3. In the post commit stage, the hook will analyze if your commit has a message of the type "version x.y.z" in the first line. If it finds it, it will check that "buildVars.py" matches the specified version. 129 | 3. If they don't match, it will update the version using updateVersion.py, add the modified file to the index, and do a commit --amend. 130 | 4. It will create a tag with the specified version. 131 | 132 | The hook will display a console message to announce that it has recognized a version indication, and the python script will too. You can check the console messages if you want to make sure that everything went well. 133 | 134 | If you use a github workflow to push releases by uploading a tag, all you have to do is enter "git push origin x.y.z". 135 | While you could add this last command to the hook, I don't think it's a good idea. But you can add it if you want. maybe one day I will. 136 | 137 | ## Notes: 138 | 139 | I usually work inside a folder called "nvda/addons", and each add-on is inside that folder. 140 | Inside nvda folder, I have a clone of the source code of nvda, it would be "nvda/nvda". 141 | 142 | I mentioning it because so, you can understand the paths pre-configured in the ".code-workspace" file for vs code. 143 | 144 | See this information in [spanish here](https://github.com/davidacm/NVDADevelopmentUtilities/blob/main/spanishReadme.md) 145 | -------------------------------------------------------------------------------- /spanishReadme.md: -------------------------------------------------------------------------------- 1 | # Utilidades de desarrollo de NVDA. 2 | Este repo contendrá todas las utilidades que he escrito o encontrado en cualquier lugar, para ayudar al desarrollo de los complementos de NVDA. 3 | 4 | ## config helper. 5 | [Encuentra la utilidad aquí](https://raw.githubusercontent.com/davidacm/NVDADevelopmentUtilities/master/src/_configHelper.py) 6 | 7 | Esta es una pequeña utilidad para manejar la configuración de NVDA en nuestros complementos. 8 | 9 | La filosofía aquí es que las cadenas relacionadas con la codificación sólo deben ser escritas en un lugar centralizado. 10 | Debido a que los IDE's usualmente no pueden ayudarte a autocompletar dentro de una cadena, entonces puedes cometer errores si cambias sólo una letra. Recuerda que, por ejemplo, una cadena como clave en un diccionario, es sensible a las mayúsculas y minúsculas. 11 | 12 | Realmente odio escribir config.conf.['a1']['a2]['option'] cada vez que 13 | necesito acceder, o establecer un valor en la configuración. 14 | 15 | ### Uso. 16 | 17 | Esto consiste en una clase para la especificación y un descriptor muy simple, para obtener y 18 | acceder a los valores de configuración. No te preocupes, no necesitas usar el descriptor directamente, el decorador de clase hará todo el trabajo por ti. 19 | 20 | Hay muchas maneras de usarlo, pero la más sencilla es usar el decorador de clase. 21 | 22 | Para acceder a la especificación de configuración, accede a la propiedad de la clase que declaraste. La instancia accederá únicamente al valor actual de la configuración. 23 | 24 | Normalmente la especificación la escribirás en un string. Pero si tu configuración se comporta de forma un poco distinta a lo usual, puedes usar una tupla de valores para modificar el comportamiento de las configuraciones. 25 | Estos son los valores soportados actualmente si usas tuplas: 26 | 27 | 1. String con la especificación de la opción. 28 | 2. True para indicar que la configuración se debe generalizar, o false para indicar que debe ser específica para cada perfil de NVDA. Por defecto es false. 29 | 3. (parámetro opcional) Una función de validación de tipos. Ya que NVDA no comprueba ni convierte tipos al acceder directamente al perfil general, puedes especificar una función para que valide de manera automática el valor de la opción. Esto tiene sentido únicamente si estableciste el segundo parámetro en True. 30 | 31 | ### ejemplo de código: 32 | 33 | Este ejemplo fue tomado del complemento [Beep Keyboard.](https://github.com/davidacm/beepKeyboard) 34 | 35 | ``` 36 | # Primero, importemos la utilidad. El decorador y la función para registrar la configuración. 37 | from ._configHelper import configSpec, registerConfig 38 | 39 | # Ahora, la declaración de la clase, con el decorador primero. 40 | # este decorador remplazará los atributos con un descriptor para manejar el acceso y la actualización de los valores de la configuración. 41 | # puedes definir la ruta de la configuración en el decorador o en la clase. 42 | # yo la prefiero en el decorador. Si la quieres en la clase, debes definir un atributo dentro de la clase así: 43 | # __path__ = "..." 44 | # la ruta en el decorador tiene prioridad sobre la declarada dentro de la clase. 45 | 46 | @configSpec('beepKeyboard') 47 | class AppConfig: 48 | # la declaración de cada configuración. En forma de nombre = 'descripción' 49 | beepUpperWithCapsLock = 'boolean(default=True)' 50 | beepCharacterWithShift = 'boolean(default=False)' 51 | beepToggleKeyChanges = 'boolean(default=False)' 52 | announceToggleStatus = 'boolean(default=True)' 53 | disableBeepingOnPasswordFields = 'boolean(default=True)' 54 | ignoredCharactersForShift = "string(default='\\x1b\\t\\b\\r ')" 55 | beepForCharacters = "string(default='')" 56 | shiftedCharactersTone = 'int_list(default=list(6000,10,25))' 57 | customCharactersTone = 'int_list(default=list(6000,10,25))' 58 | capsLockUpperTone = 'int_list(default=list(3000,40,50))' 59 | toggleOffTone = 'int_list(default=list(500,40,50))' 60 | toggleOnTone = 'int_list(default=list(2000, 40, 50))' 61 | # ahora registramos la descripción de la configuración que acabamos de hacer. 62 | AF = registerConfig(AppConfig) 63 | 64 | # accediendo al valor de una opción: 65 | print("Esto debería imprimir False", AF.disableBeepingOnPasswordFields) 66 | # Actualizando el valor: 67 | AF.disableBeepingOnPasswordFields = True 68 | # Veamos el nuevo valor. 69 | print("Esto debería imprimir True", AF.disableBeepingOnPasswordFields) 70 | ``` 71 | 72 | Beamos un ejemplo con configuraciones de perfil general. El ejemplo fue tomado del complemento [IBMTTS.](https://github.com/davidacm/NVDA-IBMTTS-Driver) 73 | 74 | ``` 75 | from ._configHelper import configSpec, registerConfig, boolValidator 76 | @configSpec("ibmeci") 77 | class _AppConfig: 78 | dllName = ("string(default='eci.dll')", True) 79 | TTSPath = ("string(default='ibmtts')", True) 80 | # opcion de perfil general con validador (bool) 81 | autoUpdate = ('boolean(default=True)', True, boolValidator) 82 | appConfig = registerConfig(_AppConfig) 83 | ``` 84 | 85 | ### Casos reales de uso: 86 | 87 | * [beepKeyboard](https://github.com/davidacm/beepkeyboard) 88 | * [SpeechHistoryExplorer](https://github.com/davidacm/SpeechHistoryExplorer) 89 | * [IBMTTS](https://github.com/davidacm/NVDA-IBMTTS-Driver) 90 | * [Wake Speaker](https://github.com/davidacm/WakeSpeaker) 91 | 92 | ## typeString. 93 | [Encuentra el código de la función aquí](https://raw.githubusercontent.com/davidacm/NVDADevelopmentUtilities/master/src/typeString.py) 94 | 95 | Este es un pequeño fragmento de código para ayudarte a escribir una cadena en una entrada de texto. A veces los desarrolladores de complementos copian un texto en el portapapeles, y luego lo pegan. Pero en mi opinión, eso no es una buena idea porque interfiere con el portapapeles del usuario. 96 | 97 | Desarrollé esta función hace mucho tiempo. Actualmente no la estoy usando, esto podría tener algunos errores. Úsalo, y si encuentras un problema y puedes arreglarlo, por favor hazme saber la solución. 98 | 99 | ### Uso 100 | 101 | Simplemente llama a la función con la cadena que necesitas escribir. Por ejemplo 102 | 103 | typeString("Hola, esto es una prueba") 104 | 105 | ## "updateVersion.py". Utilidad para actualizar la versión de buildVars.py 106 | 107 | Este pequeño script no requiere módulos externos. Simplemente es un archivo python al que le debes pasar la versión a la cual deseas actualizar el archivo buildVars, utilizado por scons para empaquetar los complementos de NVDA. 108 | 109 | Por ejemplo: 110 | 111 | "python updateVersion.py 2023.5.2" 112 | 113 | Si el script reconoce un argumento pasado al script, lo identificará como la versión que deseas asignar. 114 | 115 | ## "post-commit". Hook para actualizar la versión si olvidaste hacerlo. 116 | 117 | Esta idea nació porque siempre olvido actualizar buildVars.py, actualizar la versión de un add-on requiere actualizar lo mismo en muchas partes y no se me dan bien las tareas repetitivas. 118 | Solía actualizar la versión en un github workflow que utilizo para publicar releases automáticamente, pero no me gusta la idea de tener un buildVars desinscronizado con la última versión. 119 | 120 | Entonces, si no quieres complicarte con todo eso, deja que un hook lo haga por ti. 121 | Para poder hacerlo, se requiere del uso del script "updateVersion.py". 122 | 123 | ### Funcionamiento: 124 | 125 | 1. Pon el archivo "post-commit" dentro de la carpeta .git/hooks. 126 | 2. Si vas a liberar una nueva versión, escribe en la primera línea del commit la versión de esta forma: "version 1.2.3". La versión debe estar compuesta por tres números, dado que es requisito para la tienda de complementos de NVDA. 127 | 3. En el post commit, el hook analizará si tu commit posee en la primera línea un mensaje del tipo "version x.y.z". Si lo encuentra, verificará que "buildVars.py" coincida con la versión especificada. 128 | 4. En caso de no coincidir, actualizará la versión usando updateVersion.py, agregará el archivo modificado al índice, y hará un commit --amend. 129 | 5. Creará un tag con la versión especificada. 130 | 131 | El hook mostrará un mensaje por consola indicando que ha reconocido una indicación de versión, y el script de python también. Puedes revisar los mensajes de la consola si deseas estar seguro que todo ha ido bien. 132 | 133 | Si usas un github workflow para lanzar releases al subir un tag, solo te toca introducir "git push origin x.y.z". 134 | Si bien podrías agregar este último comando al hook, no lo considero buena idea. Pero puedes añadirlo tu si lo deseas. tal vez algún día lo haga. 135 | 136 | ## Notas: 137 | 138 | Normalmente trabajo dentro de una carpeta llamada "nvda/addons", y cada complemento está dentro de esa carpeta. 139 | Dentro de la carpeta nvda, tengo un clon del código fuente de nvda, sería "nvda/nvda". 140 | 141 | Lo menciono porque así podrás entender las rutas preconfiguradas en el archivo ".code-workspace" para usar con VS Code. 142 | --------------------------------------------------------------------------------