{"id":15021601,"url":"https://github.com/deavid/pineboo","last_synced_at":"2025-10-29T08:34:02.501Z","repository":{"id":2833959,"uuid":"3836868","full_name":"deavid/pineboo","owner":"deavid","description":"Pico Eneboo","archived":false,"fork":false,"pushed_at":"2019-08-09T15:55:57.000Z","size":32262,"stargazers_count":4,"open_issues_count":0,"forks_count":13,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-02-02T00:24:40.388Z","etag":null,"topics":["erp","parser","python","qt5"],"latest_commit_sha":null,"homepage":"https://deavidsedice.gitlab.io/pineboo/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/deavid.png","metadata":{"files":{"readme":"README.colabora.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.mit","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-03-26T20:04:25.000Z","updated_at":"2019-08-05T18:27:28.000Z","dependencies_parsed_at":"2022-08-28T16:40:31.935Z","dependency_job_id":null,"html_url":"https://github.com/deavid/pineboo","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deavid%2Fpineboo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deavid%2Fpineboo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deavid%2Fpineboo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deavid%2Fpineboo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/deavid","download_url":"https://codeload.github.com/deavid/pineboo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238795461,"owners_count":19531748,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["erp","parser","python","qt5"],"created_at":"2024-09-24T19:56:46.465Z","updated_at":"2025-10-29T08:33:53.386Z","avatar_url":"https://github.com/deavid.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Cómo colaborar en Pineboo\n=====================================\n\nPrimero de todo, hemos de entender cómo funciona internamente Pineboo. Además,\nhabremos de familiarizarnos con Python3, especialmente hay algunas técnicas más\navanzadas que se usan para \"hacer creer\" al código QS que lo que hay detrás es\nun motor QSA Qt3.\n\n\nCómo funciona Pineboo por dentro\n---------------------------------\n\nPineboo está programado como una librería, por lo que todo el código está dentro\nde la carpeta pineboolib. Los ficheros exteriores únicamente sirven como\nlanzadores. El programa principal es main.py. En él se desarrolla prácticamente\ntodo lo que hace Pineboo, hasta el punto que deberíamos intentar separarlo en\npartes más pequeñas según qué funcionalidad cubren. Ahora mismo main.py\ncontrola casi todo lo que hace pineboo.\n\nA grosso modo, el proceso de carga es el que sigue:\n\n- Conexión a la base de datos\n- Descarga de ficheros flfiles a carpeta de caché (tempdata/cache)\n- Interpretado de los ficheros XML y UI de acciones\n- Creación del formulario y rellenado con sus modulos y acciones.\n- Al hacer clic en una acción (o si pasamos el switch -a) se dispara la carga\n  de la misma, con su formulario.\n- Si el fichero .py no existe, vamos al QS, lo convertirmos en XML y después a PY\n- Se importa dinámicamente el fichero py\n- Se crea el widget de la pestaña leyendo Qt3 y generando controles Qt5\n- Se ejecuta el init de la acción\n\nDe forma más reciente, se soporta también la llamada a la acción EditRecord que\nhace algo parecido a lo anterior: (este proceso está bastante inacabado)\n\n- Se convierte el QS a PY\n- Se crea el formulario pestaña leyendo Qt3 y generando controles Qt5\n- Importamos dinámicamente el PY y ejecutamos el init.\n\nAspectos interesantes en los procesos de pineboo\n---------------------------------------------------\n\nLa descarga de ficheros de base de datos a la caché, tiene una \"heurística\" para\ndetectar la codificación correcta. No es infalible, pero parece que funciona bien.\n\nLos ficheros de la caché se guardan en carpetas con el nombre del fichero, y el\nfichero realmente tiene de nombre el hash. Esto está así a propósito. De este\nmodo mezclo las cachés de distintos programas. Si dos programas comparten un\nfichero, este no se convierte dos veces. Si un fichero se modifica, cambia de\nnombre. Esto me simplifica bastante la vida.\n\nLa lectura de los XML y los UI se hace a través de python-lxml, que tendréis que\naprender si queréis modificar o mejorar el código, aunque no creo que haga falta\nmucha colaboración en este punto concreto. Se guardan en una serie de clases,\nque la verdad, podrían estar mejor organizadas, ya que todas están en main.py y\npor otra parte hay cosas que están desperdigadas en otras clases cuando podríamos\nunificarlas por simplicidad. Hay clases diferentes para acciones según si venía\ndel xml o del ui.\n\nEstoy usando XMLStruct para mapear lo que se ve en los ficheros XML a propiedades\nde la clase. De este modo es mucho más fácil leer los ficheros. El problema es\nque en el código es un poco más complicado de seguir.\n\nRespecto al formulario principal de la aplicación, José Antonio Fernández ya lo\nmejoró bastante, y viendo cómo está ahora mismo yo, en mi opinión, focalizaría\nlos esfuerzos en otro área.\n\nLa conversión de QS a Python tiene bastante miga, en principio no espero\ncolaboración en este área por su complejidad, pero también es la que más\nacabada tenemos. Sí agradecería colaboración detectando errores traduciendo de\nQS a Python; por ejemplo si un código de QS parece que vaya a hacer otra cosa\nen Python, o no se convierte del todo, u otro error. Obviamente para que\nconvierta, tiene que parsear bien, y es mucho más sensible que Eneboo a ciertas\nformas de trabajo.\n\nLa importación dinámica de python, bueno, en principio se supone que sólo se\npueden importar ficheros de código si se conoce previamente su nombre, pero\nusando (hackeando) el código del importador, es posible decirle que importe el\nque quieras. No es muy complicado, funciona, y creo que está terminado. La\nventaja de importar dentro es que Python compila a bytecode el código fuente,\npor lo que acelera su ejecución y además se cachea en disco para que la próxima\nvez la importación sea casi instantánea.\n\nSobre la conversión de Qt3 a Qt5, lo que se hace a groso modo es leer el XML del\nfichero UI (formato Qt3) y empezamos a seguir sus instrucciones para crear un\nformulario con la misma \"receta\". El problema es que los \"ingredientes\" no son\nlos mismos en Qt4, por lo que hay que ir traduciendo nombres de propiedad o\ncontroles al vuelo. Lo más importante aquí es que un control lo busca primero\nen \"flcontrols.py\" y después lo busca en Qt4. Eso quiere decir que si defines\nen flcontrols un control que sí existe en Qt4, fuerzas al UI a cargar este\ncontrol en lugar del que viene por defecto, eso nos permite cambiarle las\npropiedades o ampliarlas.\n\nSobre la ejecución del código de la acción, actualmente solo lanzamos el init.\nHay que tener en cuenta que al principio del py traducido hay unos cuantos imports\nque facilitan emular Qt3+QSA.\n\nDónde hay que colaborar en Pineboo\n---------------------------------------\n\nLa parte donde más trabajo tenemos que poner todos es en la API de los ficheros\nQS y en los controles FL*.\n\nPor mucho que ya traducimos a Python, ahora ese código empieza a buscar funciones\nen FLUtil que no existen, los controles FLTableDB le falta casi toda la funcionalidad,\nlas FLSqlQuery no están ni implementadas, etc, etc.\n\nMuchas de esas cosas están en Qt5 mejor resueltas que en Qt3, por lo que a veces\ncon sencillos \"wrappers\" que emulen el comportamiento antiguo haría que todo\nempezase a funcionar. Pero hay mucho curro de ir función por función y enlazándola\ndonde le toca. Además hay que probar mucho código, para ir viendo cómo funciona\ntodo en la realidad.\n\nTodo esto de la API se divide en unos pocos ficheros:\n\n- **flcontrols.**\n  Aquí pondremos todo lo que sean controles visuales de Qt, especialmente\n  si queremos que qt3ui, al traducir el formulario, encuentre dónde está el control.\n- **qsaglobals.**\n  Este fichero es para las funciones y clases que sí están disponibles\n  globalmente en qsa, pero no lo están en Python. Por ejemplo \"parseFloat\" está aquí.\n- **qsatype.**\n  Es muy similar a qsaglobals, pero aquí dejo principalmente los constructores\n  de algunas clases. Hay también constructores de controles, para que cuando desde\n  QS se cree un control nuevo, podamos tener mayor control sobre lo que hará el programa.\n\nPara colaborar, lo normal es intentar ejecutar un programa completo, ir probando\ny viendo en la consola todos los mensajes que se reportan; localizar qué parte\ndel código qs no se está lanzando y decidir qué nuevas API implementar.\n\nUna vez implementemos lo nuevo, debemos comprobar que se ejecuta correctamente,\nal menos lo que nosotros hemos programado.\n\nMuchas de las clases y funciones tienen triquiñuelas para evitar que de error\ncuando el QS haga algo para lo que no está programado. El primero que hice es\nel decorador \"NotImplementedWarn\", y más adelante hice el \"DefFun\". El primero\nhay que ponerlo función a función. El segundo solo una vez por clase. La funcionalidad\nde ambos es que cuando se llame a algo inexistente, lo informe por la consola,\npero que emule el método devolviendo un valor por defecto, permitiendo que el\ncódigo se ejecute más allá del error.\n\n\n\n\nCómo funciona la conversión a Python\n--------------------------------------\n\nLa conversión de ficheros de QS a Python se hace en dos pasos, primero de QS a\nXML y luego de XML a Python. Lo tenéis todo en la carpeta flparser.\n\nEl primer paso convierte el fichero QS en un XML. Consiste internamente en:\n\n- **parsear.**\n  Usamos para esto python-ply, lee el código, separa las distintas\n  palabras claves, números, textos (esto se conoce como lexer y está en flex.py).\n  Después procesamos el resultado siguiendo unos patrones. (flscriptparse.py)\n  En estos patrones no se han configurado tal y como especifica el\n  estándar Ecmascript, sino de un modo más comprensivo y similar a como nosotros\n  programamos. El resultado es que el parser entiende mejor el sentido del programa,\n  pero por contra, no parsea todos los programas que se pueden hacer en QSA.\n  Hay algunos patrones de programación que no va a reconocer, pero por lo general\n  suelen ser dañinos y deberíamos cambiarlos.\n- **generación árbol AST.**\n  AST significa \"Abstract Syntax Tree\" y básicamente es\n  una representación de nodos donde está la información recolectada por el parser.\n  Aunque el parser se intenta que sea más listo con los patrones que lee, las\n  estructuras generadas en este paso siguen siendo demasiado complejas y liadas\n  como para que otro programa \"entienda\" qué está haciendo. (flscriptparse.py)\n- **simplificar el árbol.**\n  Dado que el AST es aún demasiado complicado, lo que\n  hacemos es aplicar unas conversiones. Cuando se detectan ciertos patrones\n  repetitivos, se resumen en un solo nodo. (postparse.py)\n- **guardar a XML.**\n  El resultado, lo guardamos en un fichero XML. El objetivo es\n  principalmente, permitir la depuración \"a mano\". Si algo va mal, el XML es el\n  punto intermedio que nos permite saber en qué parte del programa tenemos que\n  modificar. Una cosa que falta a futuro, es eliminar este paso intermedio\n  (ahorrariamos CPU de grabar y leer XML, que no es poco) y dejarlo como opción\n  para depurar. (postparse.py)\n\nEl segundo paso lee el XML y lo convierte en un fichero Python. Este es mucho\nmás sencillo que el anterior. Lo que hacemos es convertir cada nodo del xml\nen un patrón de programación de Python. Cuando algo no se reconoce, hay que\nagregar un nuevo patrón de programación. Algunos son más complicados o confictivos\nque otros. Todo esto está controlado en el fichero pytnyzer.py.\n\nEn este último paso, me encargo de modificar lo que se escribe a Python. Por\nejemplo se introducen una serie de encabezados al inicio del fichero. También\ndetecto ciertas llamadas/patrones y reemplazo por otro contenido.\n\nUn ejemplo de esto es la clase \"qsa\", que cuando el parser detecta llamadas a\npropiedades conflictivas (por ejemplo antes era posible hacer child(\"x\").text = \"1\"\ny ahora hay que hacer un setText(\"1\")) consigue detectar más o menos el tipo de\nllamada que pretende y lanzar en su lugar la correcta. De ese modo el código\nqsa sigue funcionando. Este problema lo hemos tenido ya en Eneboo al mejorar\nsu parseador (cuando lo hizo infoSial en AbanQ), y también ocurre de forma\nmucho más grave cuando intentas lanzar en Qt4 el QtScript, ya que ocurre\nexactamente lo mismo, lo que antes eran propiedades ahora son funciones.\n\nPor eso, cuando veáis el código Python, algunas partes las veréis cambiadas.\nAl principio tendremos que depurar cuidadosamente para estar seguros de\nque estamos ejecutando lo mismo que antes.\n\nCómo funciona la conversión de Qt3 a Qt5\n------------------------------------------\n\nTodo empieza con la función loadUi de qt3ui.py. En ella leemos a mano el fichero,\ny identificamos las conexiones a realizar, las imágenes y \"el widget\".\n\nEl widget, que sería alo como el widget raíz, es donde empieza lo complicado.\nDentro del widget se define todo el formulario en forma de layouts y más widgets.\n\nLa función \"loadWidget\" se encarga de este dilema recursivo. Tienen toda la\nlógica necesaria y se llama a sí misma. La clave reside en el \"for c in xml\",\nel cual procesa todas las ordenes del ui para este widget. Según el tipo de\netiqueta realizamos una u otra acción. Cuando encontramos otro widget, lo creamos\nusando la función \"createWidget\" que nos busca y nos crea la clase adecuada.\nA partir de aquí se llama a sí mismo para completar la carga del widget de forma\nrecursiva.\n\nLo más curioso de esta función es que traduce unas propiedades por otras\n(translate_properties) y para asignarlas, busca una función setXyz para una\npropiedad xyz.\n\nDe momento lo que más faena da son los FLTableDB dentro de pestañas (QTabWidget).\nDado que este control se inicializa en su construcción, por el modo en que se\nconstruye en este caso, queda mal inicializado.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeavid%2Fpineboo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdeavid%2Fpineboo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeavid%2Fpineboo/lists"}