dstroy0 / InputHandler

Arduino input handler
https://dstroy0.github.io/InputHandler/
GNU General Public License v3.0
1 stars 0 forks source link

lib file auto-generation script #52

Closed dstroy0 closed 2 years ago

dstroy0 commented 2 years ago

I'm going to start work on one in python, since I don't know much python this'll be fun. It'll be aimed at user commands.

I'm looking at using tkinter to build the gui, again I don't know how to yet so this may change.

Require the user to copy/paste local/local include the library, add one line to their setup and loop functions. It should generate/edit: InputHandler/src/config/config.h - e ih_cli/ - g ih_cli/cli_setup.h - g ih_cli/cli_commandparameters.h - g ih_cli/cli_functions.h - g ih_cli/cli_functions.cpp - g

2bndy5 commented 2 years ago

os.uname is platform specific. Its better to use platform.uname(). It was designed to be a drop-in replacement for better cross-platform code. Here's a test from a python interactive REPL:

>>> import platform
>>> uname_tuple = platform.uname()
>>> print(uname_tuple)
uname_result(system='Windows', node='B-DESKTOP', release='10', version='10.0.22000', machine='AMD64')
>>> print(uname_tuple.system)
Windows
dstroy0 commented 2 years ago

I'm working on the tab button dialog boxes today.

dstroy0 commented 2 years ago

Got a bit done today, it's pretty neat! I'm figuring out how to use the signals, had to do that to auto update the command length.

dstroy0 commented 2 years ago

I made an object to put commandParameters into when we pull in all fields in the command settings menu with apply.

dstroy0 commented 2 years ago

Input validation is very nice, I made it for the function name, command string, and the ids. the regexp are just temporary until I get the settings_tree menu complete, I'm planning on generating that from config.h after I smoosh config and advanced_config together. Then parsing that file, and setting those regexp based off of that input.

dstroy0 commented 2 years ago

We can add items to the tree on InputHandler settings tab, and only the 3rd col is editable by double clicking. Now I'm going to work on parsing user settings out of config.h.

dstroy0 commented 2 years ago

Figuring out little bits at a time, this thing is really cool. I keep getting distracted and going on little tangents. I'm having so much fun when I'm working on it. I'm really close to being able to parse the config file the way I want to; but then I learn something new and refactor things. I absolutely love this part of learning, when everything is new and fresh. It's like leveling up in a really good rpg or something. I hope you've been well. If you've got any thoughts on anything please let me know. Happy belated father's day if you're a dad.

dstroy0 commented 2 years ago

parsing the settings out of the config file, they're going to be the 4th match group (\s*[\/]*\s*)(\s*#define\s*)(UI_\S*\s*)(\d*) and progmem is differentiated like so (\s*[\/]*\s*)(\s*#define\s*)(UI_\S*PGM_LEN\s*)(\d*)

dstroy0 commented 2 years ago

The settings tree is populating from config.h. I'm going to put the whole tree into a dict. Now I have to connect up the edit to the list of strings that makes up config.h, and save everything to the session json. Pretty cool!

2bndy5 commented 2 years ago

Will there also be an option to import a config from json?

dstroy0 commented 2 years ago

Yes, that's the idea, speed and ease of setup.

2bndy5 commented 2 years ago

Awesome! That would definitely help with sharing/tweaking known-good configs. 😃

dstroy0 commented 2 years ago

I'm adding cli_gen_tool.json (the session json) to the ignore list so that a new one is generated on first run. Also so I don't accidentally push mine out after messing with unimplemented things.

2bndy5 commented 2 years ago

Couldn't you just use a virtual json if a json file does not exist? I like the gitignore idea.

import json, os

default_config = dict(
    key="value",
    # ...
)

if os.path.isfile("config.json"):
    with open("config.json", "r", encoding="utf_8") as conf:
        config = json.load(conf)
else:
    config = default_config.copy()
dstroy0 commented 2 years ago

Yes, it's a QFile so I can just https://doc.qt.io/qt-6/qfile.html#exists-1

file = QFile(path)
        if not file.exists():
            self.session = json.loads(self.defaultGuiOpt)
2bndy5 commented 2 years ago

Yeah, that's sounds right. I'm so used to working with python std libs. 😊

dstroy0 commented 2 years ago

I really like how I can shorten variable access in python it makes it much more readable

dstroy0 commented 2 years ago

combo boxes for true/false fields pass their sub dict that contains text fields and the qtreewidgetitem and qcombobox objects on index change. Everything emits a signal when edited in the settings tree now. Time to work on the code preview for config.h. It will be updated with every item change, which is nice!

dstroy0 commented 2 years ago

config.h file preview is working

dstroy0 commented 2 years ago

I just did it quick and dirty to get an idea of how to work with the QPlainTextEdit object that's doing the preview for us. It's a one way preview.

dstroy0 commented 2 years ago

image

dstroy0 commented 2 years ago

I need to add an output category as another root object so that setup.h can be generated

2bndy5 commented 2 years ago

This is starting to look close to finished or at least a working proof of concept. I wish the QT implementation for desktops supported the OS's color scheme (light vs dark). It'd also be nice if the text viewer had some syntax highlighting incorporated, but I don't know if that's a task for other libraries like pygments.

dstroy0 commented 2 years ago

There are rich text widgets and a browser so I think that's possible, the backend is a little messy and needs organized to make it easy but highlighting what the user has changed is possible based on other projects I've looked at, it would just hook into the dict and make a has_changed field true/false or something. I've been thinking about that and I think that if I can change the copy action to just pick up the plain text that would be best. Because even though we would like people to press the generate button they might want to copy paste and do their own thing.

dstroy0 commented 2 years ago

We can change the theme from light to dark as well I don't know if there's a toggle for it programmatically but it's an option I can set in the designer

2bndy5 commented 2 years ago

We can change the theme from light to dark as well I don't know if there's a toggle for it programmatically but it's an option I can set in the designer

If it needs to be hard coded, then I'd vote for dark theme. Its better for those of us spending extraordinary amounts of time looking at screens. Of course, this is your app, so the final decision should be your preference.

2bndy5 commented 2 years ago

Everything I use is set to dark theme (when possible). image

dstroy0 commented 2 years ago

Yeah all of mine are as well I just hadn't even thought about doing it.

dstroy0 commented 2 years ago

I can do it with a stylesheet or by setting up a palette manually and passing that to the app in main with setStyleSheet or setPalette respectively

2bndy5 commented 2 years ago

That may be more work than it is worth at this stage. The QT docs have a page with some options: https://wiki.qt.io/Gallery_of_Qt_CSS_Based_Styles

After a quick look, I think

dstroy0 commented 2 years ago

I just used someone's module. The geometry needs adjusting but it looks pretty good. Dark theme it up.

dstroy0 commented 2 years ago

There's a ton of little things to do but the basic poc for most things are in place. I'm going to work on that output section in settings_tree to generate setup.h and then it'll be time to work on generating commands and functions. Separately, I think everything needs like 5 pixels because of the rounded corners some text is getting cut off.

dstroy0 commented 2 years ago

the code preview update is updating the wrong text widgets I'll have to look into that tomorrow

dstroy0 commented 2 years ago

something is happening here:

# build code preview trees
    def build_code_preview_tree(self):
        for key in self.code_preview_dict['files']:
                self.code_preview_dict['files'][key]['filename'] = key 
        for tab in range(0,2):            
            if tab == 0:
                tree = self.ui.codePreview_1
            else:
                tree = self.ui.codePreview_2
            tree.setHeaderLabels(['File', 'Contents'])
            tree.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)
            tree.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
            tree.setColumnCount(2)                                           
            for key in self.code_preview_dict['files']:
                self.code_preview_dict['files'][key]['tree_item'][tab] = QTreeWidgetItem(tree, [key,''])
                self.code_preview_dict['files'][key]['tree_item'][tab].setIcon(0, self.ui.fileIcon)                
                self.code_preview_dict['files'][key]['text_widget'][tab] = QPlainTextEdit()                
                self.code_preview_dict['files'][key]['text_widget'][tab].setObjectName(str(key))                                       
                self.code_preview_dict['files'][key]['contents_item'][tab] = QTreeWidgetItem(self.code_preview_dict['files'][key]['tree_item'][tab])                                    
                tree.setItemWidget(self.code_preview_dict['files'][key]['contents_item'][tab], 0, self.code_preview_dict['files'][key]['text_widget'][tab])
                print(self.code_preview_dict['files'][key]['text_widget'][tab])
                self.code_preview_dict['files'][key]['contents_item'][tab].setFirstColumnSpanned(True)            
                self.code_preview_dict['files'][key]['file_lines_list'] = self.cliOpt['config']['file_lines']
                if key == 'config.h':
                    new_line_list = []
                    code_string = ''
                    for line in self.code_preview_dict['files']['config.h']['file_lines_list']:
                        new_line_list.append(line+'\n')
                        code_string = code_string + line + '\n'
                    self.code_preview_dict['files']['config.h']['file_lines_list'] = new_line_list                
                    self.code_preview_dict['files']['config.h']['text_widget'][tab].setPlainText(code_string)
        for key in self.code_preview_dict['files']:
            print(key)
            print(self.code_preview_dict['files'][key]['text_widget'])                                                                                                  
    # end build_code_preview_tree()

this is the term out:

<PySide6.QtWidgets.QPlainTextEdit(0x20a05478840, name="config.h") at 0x0000020A05AAEAC0>
<PySide6.QtWidgets.QPlainTextEdit(0x20a054e2a70, name="setup.h") at 0x0000020A05AB3040>
<PySide6.QtWidgets.QPlainTextEdit(0x20a054e4360, name="parameters.h") at 0x0000020A05AB30C0>
<PySide6.QtWidgets.QPlainTextEdit(0x20a054e42f0, name="functions.h") at 0x0000020A05AB3200>
<PySide6.QtWidgets.QPlainTextEdit(0x20a054e6900, name="functions.cpp") at 0x0000020A05AB3340>
<PySide6.QtWidgets.QPlainTextEdit(0x20a054e0a10, name="config.h") at 0x0000020A05AB3500>
<PySide6.QtWidgets.QPlainTextEdit(0x20a055b4630, name="setup.h") at 0x0000020A05AB3640>
<PySide6.QtWidgets.QPlainTextEdit(0x20a055b6af0, name="parameters.h") at 0x0000020A05AB3780>
<PySide6.QtWidgets.QPlainTextEdit(0x20a055b6d20, name="functions.h") at 0x0000020A05AB38C0>
<PySide6.QtWidgets.QPlainTextEdit(0x20a055baf30, name="functions.cpp") at 0x0000020A05AB3A00>
config.h
{0: <PySide6.QtWidgets.QPlainTextEdit(0x20a054e6900, name="functions.cpp") at 0x0000020A05AB3340>, 1: <PySide6.QtWidgets.QPlainTextEdit(0x20a055baf30, name="functions.cpp") at 0x0000020A05AB3A00>}
setup.h
{0: <PySide6.QtWidgets.QPlainTextEdit(0x20a054e6900, name="functions.cpp") at 0x0000020A05AB3340>, 1: <PySide6.QtWidgets.QPlainTextEdit(0x20a055baf30, name="functions.cpp") at 0x0000020A05AB3A00>}
parameters.h
{0: <PySide6.QtWidgets.QPlainTextEdit(0x20a054e6900, name="functions.cpp") at 0x0000020A05AB3340>, 1: <PySide6.QtWidgets.QPlainTextEdit(0x20a055baf30, name="functions.cpp") at 0x0000020A05AB3A00>}
functions.h
{0: <PySide6.QtWidgets.QPlainTextEdit(0x20a054e6900, name="functions.cpp") at 0x0000020A05AB3340>, 1: <PySide6.QtWidgets.QPlainTextEdit(0x20a055baf30, name="functions.cpp") at 0x0000020A05AB3A00>}
functions.cpp
{0: <PySide6.QtWidgets.QPlainTextEdit(0x20a054e6900, name="functions.cpp") at 0x0000020A05AB3340>, 1: <PySide6.QtWidgets.QPlainTextEdit(0x20a055baf30, name="functions.cpp") at 0x0000020A05AB3A00>}
dstroy0 commented 2 years ago

Whatever is happening is what's causing the text to get refreshed in the incorrect QPlainTextEdit widget.

dstroy0 commented 2 years ago

I think it might have something to do with the way I built the dict

dstroy0 commented 2 years ago

Is this a nono?

generated_filename_subdict = {'filename':'',
                              'file_lines_list':[],
                              'tree_item':{},
                              'contents_item':{},
                              'text_widget':{0:'',1:''}}
#generated_filename_dict = OrderedDict()
generated_filename_dict = {'config.h':generated_filename_subdict,
                           'setup.h':generated_filename_subdict,
                           'parameters.h':generated_filename_subdict,
                           'functions.h':generated_filename_subdict,
                           'functions.cpp':generated_filename_subdict}
dstroy0 commented 2 years ago

Yeah, it looks like the generated_filename_subdict is pointed at by all of the keys

dstroy0 commented 2 years ago

Yes, that was it.

dstroy0 commented 2 years ago

Now that's all fixed, I made it so the text pops to where the user made a change like you were talking about, and we can highlight the change individually so that's probably where we can wrap it with some html to change the color.

2bndy5 commented 2 years ago

All Python objects are just pointers under the hood. If you want to use a copy of a dict, then call <dict_name>.copy().

dstroy0 commented 2 years ago

I tried that, it didn't work. I think because copy only makes a shallow copy.

dstroy0 commented 2 years ago

What would have worked is probably generated_filename_dict['config.h'] = generated_filename_subdict.copy() but I think the copy would be redundant since the assignment operator does the deep copy right?

2bndy5 commented 2 years ago

There is also a deepcopy() method in a std lib called copy. So

from copy import deepcopy

some_dict = dict(key="value")
copied_dict = deepcopy(some_dict)

Assignment operators are actually storing the pointer to an object and increasing the object's internal reference counter by 1. The reference counter is how python does garbage collection.

dstroy0 commented 2 years ago

The method organization is driving me nuts; I keep hesitating organizing them because I merge, cut and paste them constantly but there are so many now it's slowing me down. I think by reference order in init would probably make the most sense?

2bndy5 commented 2 years ago

Yeah I figured the file length was eventually going to be a problem. You could split it up into classes (which could be imported) or just put functions into separate files that are imported as modules.

Using pylint helped me a lot when I was getting the hang of python. Some rules seem ridiculous, but they are born of best practices. There is a way to have vscode use pylint in the python extension settings.

Pylint also has duplicate code checking, just as a warning.

2bndy5 commented 2 years ago

BTW, It is pretty common to use black as a code formatter tool in python. In fact if you right click the python code (anywhere) and select "Format Document", then vscode should automatically ask you if it is ok to install black.

dstroy0 commented 2 years ago

Cool, I installed the black extension and used it on the code.

2bndy5 commented 2 years ago

You'll notice that the indentation is not the same as C++ convention. This is because whitespace is not ignore by the python interpreter (though a C++ compiler does ignore whitespace). So, indentation is rather important, thus it is a part of the PEP8 conventional standards.

Using black will let you get on with your life and still adhere to PEP8 standards.

2bndy5 commented 2 years ago

I found that using a stub lib for c-extensions (like pyside) will help with auto-completion in vscode. Installing https://pypi.org/project/IceSpringPySideStubs-PySide6/ will help with that when using pyside6 API. You may have to restart vscode (or do a window reload) for the stub lib to get loaded properly.

A sub lib tells IDEs what data type is used for objects in python code. They also have the benefit of listing the exposing members of modules and data structures.