Browser and install GUI for cookiecutter templates

⌈⌋ ⎇ branch:  cookiedough


Check-in [0adcc047dd]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Removed remnants of previous attempts of search input widget prettification
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 0adcc047dd807da68e336e9938d3babad273409d1173016d1cf4c606d3d2741e
User & Date: mario 2021-03-22 18:11:58
Context
2021-03-22
18:12
Implement patch for cookiecutter/config.py (xdg paths) check-in: cf74a883a3 user: mario tags: trunk, 0.0.9
18:11
Removed remnants of previous attempts of search input widget prettification check-in: 0adcc047dd user: mario tags: trunk
18:10
Fixed doc references to old search behaviour check-in: 425ba2342d user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to cookiedough/__init__.py.

127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148

149
150
151
152
153
154
155
156
157
158
159
        return sorted(ls, key=f, reverse=reverse)

repos = repos()


#-- widget structure
menu = [
    ["File", ["Working directory", "Settings", "Cookiecutter", ["patch for xdg compliance", "cookiecutter config", "defaults"], "---", "About", "Exit"]],
    ["Template", ["Install", "URL", "Copy Repo URL", "Report", "Update", "Details"]],
    ["Help", ["Help", "Wiki", "About"]],
]
layout = [[
    sg.Menu(menu, key="menu", font="Sans 11"),
    # left pane: blue/gray
    sg.Column(size=(320,725), background_color="#343131", pad=(0,0), layout=[
            #   
        [sg.Column(  background_color="#2980b9", size=(320,105), pad=(0,0), element_justification="center", layout=[
                                                    #
            [ sg.T("â–™  cookiedough", text_color="#fff", pad=[(20,0),(10,0)], background_color="#2980b9", font="Sans 12 bold") ],
            [ sg.T(__meta__["version"], text_color="#65a3c8", pad=[(20,0),(0,5)], background_color="#2980b9") ],
            [                                            # 
                #sg.Image(data=icons.search_bg, key="search_img",     pad=(3,0), background_color="#2980b9",   enable_events=True, visible=True),

                sg.Image(data=icons.bb1, pad=[(20,0),(0,0)], key="bb1"),
                sg.Input("    ", key="search", font="Sans 12", size=(26,1), pad=(0,0), background_color="#fefefe", enable_events=conf["search_keypress"], border_width=0, visible=True),
                sg.Image(data=icons.bb2, pad=(0,0), key="bb2"),
            ],                                #
        ])],
        [sg.Tree(
            repos.tree(), headings=["f"], col0_width=23, col_widths=[3], max_col_width=20, auto_size_columns=False,
            show_expanded=False, background_color="#353232", num_rows=30, pad=((5,0),(10,0)), justification="left",
            header_background_color="#353232", header_text_color="#333", selected_row_colors=("#2e2a2a","#a4a4a4"),
            enable_events=True, key="template"
        )]







|







<

<


<
<
>



|







127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

142

143
144


145
146
147
148
149
150
151
152
153
154
155
156
        return sorted(ls, key=f, reverse=reverse)

repos = repos()


#-- widget structure
menu = [
    ["File", ["Working directory", "Settings", "Cookiecutter", ["cookiecutter config", "defaults", "patch for xdg compliance"], "---", "About", "Exit"]],
    ["Template", ["Install", "URL", "Copy Repo URL", "Report", "Update", "Details"]],
    ["Help", ["Help", "Wiki", "About"]],
]
layout = [[
    sg.Menu(menu, key="menu", font="Sans 11"),
    # left pane: blue/gray
    sg.Column(size=(320,725), background_color="#343131", pad=(0,0), layout=[

        [sg.Column(  background_color="#2980b9", size=(320,105), pad=(0,0), element_justification="center", layout=[

            [ sg.T("â–™  cookiedough", text_color="#fff", pad=[(20,0),(10,0)], background_color="#2980b9", font="Sans 12 bold") ],
            [ sg.T(__meta__["version"], text_color="#65a3c8", pad=[(20,0),(0,5)], background_color="#2980b9") ],


            [
                sg.Image(data=icons.bb1, pad=[(20,0),(0,0)], key="bb1"),
                sg.Input("    ", key="search", font="Sans 12", size=(26,1), pad=(0,0), background_color="#fefefe", enable_events=conf["search_keypress"], border_width=0, visible=True),
                sg.Image(data=icons.bb2, pad=(0,0), key="bb2"),
            ],
        ])],
        [sg.Tree(
            repos.tree(), headings=["f"], col0_width=23, col_widths=[3], max_col_width=20, auto_size_columns=False,
            show_expanded=False, background_color="#353232", num_rows=30, pad=((5,0),(10,0)), justification="left",
            header_background_color="#353232", header_text_color="#333", selected_row_colors=("#2e2a2a","#a4a4a4"),
            enable_events=True, key="template"
        )]
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
        self.w.read(timeout=1)
        self.w["menu"].Widget.configure(borderwidth=0, type="menubar")
        self.w["template"].Widget.configure(show="tree") # borderwidth=0
        self.w["search"].Widget.bind("<Return>", self.search_enter)
        self.w["bb1"].Widget.configure(borderwidth=0)
        self.w["bb2"].Widget.configure(borderwidth=0)
        self.w["template"].set_focus()
        #self.search_bg();

    
   # add to *win_map{} event loop
    def win_register(self, win, cb=None):
        if not cb:
            def cb(event, data):
                win.close()







<







222
223
224
225
226
227
228

229
230
231
232
233
234
235
        self.w.read(timeout=1)
        self.w["menu"].Widget.configure(borderwidth=0, type="menubar")
        self.w["template"].Widget.configure(show="tree") # borderwidth=0
        self.w["search"].Widget.bind("<Return>", self.search_enter)
        self.w["bb1"].Widget.configure(borderwidth=0)
        self.w["bb2"].Widget.configure(borderwidth=0)
        self.w["template"].set_focus()


    
   # add to *win_map{} event loop
    def win_register(self, win, cb=None):
        if not cb:
            def cb(event, data):
                win.close()
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
                    while (line := next(line_iter)) and not re.match("^\s*```.*", line):
                        print(line, background_color="#eeffcc")
                except:
                    continue # on StopIteration
            else:
                print(line)

    # fake search bar clicked, exchange for real input field
    def search_img(self, data):
        self.w["search_img"].update(visible=False)
        self.w["search"].update(visible=True)
        self.w["search"].set_focus()
    # search
    def search(self, data):
        keywords = re.findall("\S+", data["search"])
        try:
            ls = repos.search(keywords)
            self.w["template"].update(repos.tree(ls))
        except:
            pass # probably an invalid regex
    # tk event: enter pressed in search box
    def search_enter(self, tk_event):
        data = { "search": self.w["search"].get() }
        self.search(data)
        
    # TEST: try to draw (round border bg) under widget,
    # there's a canvas already three levels up;
    #
    #    .!toplevel.!frame.!tkfixedframe.!canvas.!frame.!frame.!tkfixedframe.!canvas.!frame.!frame3.!entry
    #
    # Unfortunately both of the frames seem to block sight regardless of background=
    #
    def search_bg(self):
        print(dir(self.w))
        w = self.w.TKroot.nametowidget(".!toplevel.!frame.!tkfixedframe.!canvas.!frame.!frame.!tkfixedframe.!canvas")
        print(dir(w))
        #w.configure(background="")
        w.master.configure(background="")  # frame around entry widget
        w.master.master.configure(background="") # wrapper frame
        canvas = w
        print(canvas)
        print(dir(canvas))
        print(canvas.winfo_x(), canvas.winfo_y(), canvas.winfo_width(), canvas.winfo_height())
        canvas.create_oval(10, 15, 300, 95, fill="white")

        

    # Template: Install (actually do something...)
    def install(self, data):
        try:
            repo = repos.ls[data["template"][0]]
        except:







<
<
<
<
<












<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







336
337
338
339
340
341
342





343
344
345
346
347
348
349
350
351
352
353
354





















355
356
357
358
359
360
361
                    while (line := next(line_iter)) and not re.match("^\s*```.*", line):
                        print(line, background_color="#eeffcc")
                except:
                    continue # on StopIteration
            else:
                print(line)






    # search
    def search(self, data):
        keywords = re.findall("\S+", data["search"])
        try:
            ls = repos.search(keywords)
            self.w["template"].update(repos.tree(ls))
        except:
            pass # probably an invalid regex
    # tk event: enter pressed in search box
    def search_enter(self, tk_event):
        data = { "search": self.w["search"].get() }
        self.search(data)





















        

    # Template: Install (actually do something...)
    def install(self, data):
        try:
            repo = repos.ls[data["template"][0]]
        except:
427
428
429
430
431
432
433

434
435

436








437
438
439
440
441
442
443
    # File: Settings - remapped to pluginconf window
    def settings(self, data):
        files = [f"{__dir__}/*.py"]
        save = pluginconf.gui.window(conf, conf["plugins"], files=files, theme="Default1")
        if save:
            os.makedirs(re.sub("[\w.]+$", "", conf["conf_file"]), 0o755, True)
            json.dump(conf, open(conf['conf_file'], "w", encoding="utf8"), indent=4)

    # File: Cookiecutter: config
    def cookiecutter_config(self, data):

        os.system(f"{conf['editor']} ~/.config/cookiercutter/rc &")









    # Help: About
    def about(self, data):
        m = __meta__
        sg.popup(f"{m['title']} {m['version']}\n{m['description']}\n\n{m['doc']}\n")
    # Help: Wiki
    def wiki(self, uu):







>


>
|
>
>
>
>
>
>
>
>







397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
    # File: Settings - remapped to pluginconf window
    def settings(self, data):
        files = [f"{__dir__}/*.py"]
        save = pluginconf.gui.window(conf, conf["plugins"], files=files, theme="Default1")
        if save:
            os.makedirs(re.sub("[\w.]+$", "", conf["conf_file"]), 0o755, True)
            json.dump(conf, open(conf['conf_file'], "w", encoding="utf8"), indent=4)

    # File: Cookiecutter: config
    def cookiecutter_config(self, data):
        ccc = rollout.CookieCutterConfig()
        os.system(f"{conf['editor']} {ccc.fn} &")
    # File: Cookiecutter: patch
    def patch_for_xdg_compliance(self, data):
        if sg.popup_yes_no("Modify `cookiecutter/config.py` directly to use only new `~/.config/cookiecutter/` directory?") != "Yes":
            return
        ccc = rollout.CookieCutterConfig()
        #ccc.patch()
        ccc.move()
        ccc.create()

    # Help: About
    def about(self, data):
        m = __meta__
        sg.popup(f"{m['title']} {m['version']}\n{m['description']}\n\n{m['doc']}\n")
    # Help: Wiki
    def wiki(self, uu):

Added cookiedough/__main__.py.





















>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
# api: python
# type: virtual
# category: hidden
# title: module
# description: python -m invocation shortcut
# version: 0.0
# priority: never

import cookiedough
cookiedough.main()

Changes to cookiedough/icons.py.

1
2
3
4
5
6
7
8
9
10
11

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# api: pysimplegui
# type: data
# category: image
# title: icons
# description: embedded PNG files for the GUI
# version: 0.0
#
# Icons for the UI.
#



# base64 -w120 bb1.png
bb1 = b"""
iVBORw0KGgoAAAANSUhEUgAAAAsAAAAXCAIAAABS2iKRAAAACXBIWXMAABXXAAAV1wEELLsZAAABWklEQVQoz4WSPU/CYBSFz3tb2rfUKFqoIMFgqhgxxkTB
RU3YXV1dnP0R/i2jG6u6kSgDYVCMYCtoGqP9cqipCAXPcIf7PDnLvWzj/AJj0QAO6BoUkDjCdKCsqZUcXzPUgj6rSvIfY0vD8XZ6f7OY4kpCShBjAMRhfHZo
7KxmOeeMMcZYuBeHcaWUl2UpYizqWGY43TMqpaUIRxIAAnBU1arlRUmKwQAoCxwUCzOSHIsBUDmNnK4IAo2zH2Mlr80lxUkFAMhIzQuCgMkhgQhTQ36Af4x2
p+/4/jTj3u2+2U4QBADCOWp0Wuj23n3fj8UAqAnUG+Zgcg0BuGo83Ta7nufFSkKmdmICL5aZTkp5TSWimNsCuLaAehPA7nouKYnhf4RlQqZ2EkqdDzz3TNcO
NDXBuRj2BEHwa4TS3cOg1R489s3Xvut4zueXO/rJPeDSsm8sm8PKLEABfQNeZXaBguM+ZwAAAABJRU5ErkJggg==
"""
bb2 = b"""
iVBORw0KGgoAAAANSUhEUgAAAAsAAAAXCAIAAABS2iKRAAAACXBIWXMAABXXAAAV1wEELLsZAAABbklEQVQoz4WSvUoDQRSFz0wmKAajIkZcLMQiQtJZBuzs
fQYfw9cQa2sLW0G0kmhjgj+oEaNLYoLmb3cTQ9bdnezOWKyuiVniLS4XzseZc2eGbO+eWhAtAzZgIKTIa83o2bzasEq69qg6N4bZ+kO4rgtASukK0dLMs/vG
yVMlP+DGKKX+RClVFuNbc1PLiUlkiwHECCG+ByGEEBKbiGZSCoAAYt+HDXCMsUxK4Rzd42JRgg6FIsTvkUhkPZnYTC8Bw8QgNBOLbqTnk6NEwFFKEwvTymoY
EdjEY9E1lgj38CtK6YoyO44AQAn+ITwhxhGe56mddgghpfT7x6dbetPDPaSUnidqTaugjeQIDHrcOS9X639uLJA557lC4yin/76cr/mD4/B88X3/Uq1IAGCD
mpTStu2rl/peVr3Tf/6HEAKAkLLP+x3bungoH95qgQyAGe2OyZ1qs/usmvmaXdDN5nB2tnNwbUE0ddiAHrb5F4YPydfUz8N6AAAAAElFTkSuQmCC
"""

#  base64 -w120 dev/bg.png
search_bg = b"""
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
"""

#  base64 -w120 logo.png
logo = b"""
iVBORw0KGgoAAAANSUhEUgAAAF8AAAA8CAYAAAAALGYBAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAyQAAAMkBxro13wAAABl0RVh0U29mdHdhcmUAd3d3
Lmlua3NjYXBlLm9yZ5vuPBoAAAANdEVYdEF1dGhvcgBuYmNvcnDzKdAeAAAAOXRFWHREZXNjcmlwdGlvbgBEb3VnaCBhbmQgcm9sbGluZyBwaW4sIHNpbXBs
ZSBkZXNpZ24uIFl1bW15ICEKnWXDAAAAIXRFWHRDcmVhdGlvbiBUaW1lADIwMTEtMTAtMjdUMTE6MzA6NDGL6CHJAAAAPHRFWHRTb3VyY2UAaHR0cHM6Ly9v
cGVuY2xpcGFydC5vcmcvZGV0YWlsLzE2NDkyOS9kb3VnaC1ieS1uYmNvcnDbKtIwAAAAWHRFWHRDb3B5cmlnaHQAQ0MwIFB1YmxpYyBEb21haW4gRGVkaWNh











>
















<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

























29
30
31
32
33
34
35
# api: pysimplegui
# type: data
# category: image
# title: icons
# description: embedded PNG files for the GUI
# version: 0.0
#
# Icons for the UI.
#


#-- round borders for search field
# base64 -w120 bb1.png
bb1 = b"""
iVBORw0KGgoAAAANSUhEUgAAAAsAAAAXCAIAAABS2iKRAAAACXBIWXMAABXXAAAV1wEELLsZAAABWklEQVQoz4WSPU/CYBSFz3tb2rfUKFqoIMFgqhgxxkTB
RU3YXV1dnP0R/i2jG6u6kSgDYVCMYCtoGqP9cqipCAXPcIf7PDnLvWzj/AJj0QAO6BoUkDjCdKCsqZUcXzPUgj6rSvIfY0vD8XZ6f7OY4kpCShBjAMRhfHZo
7KxmOeeMMcZYuBeHcaWUl2UpYizqWGY43TMqpaUIRxIAAnBU1arlRUmKwQAoCxwUCzOSHIsBUDmNnK4IAo2zH2Mlr80lxUkFAMhIzQuCgMkhgQhTQ36Af4x2
p+/4/jTj3u2+2U4QBADCOWp0Wuj23n3fj8UAqAnUG+Zgcg0BuGo83Ta7nufFSkKmdmICL5aZTkp5TSWimNsCuLaAehPA7nouKYnhf4RlQqZ2EkqdDzz3TNcO
NDXBuRj2BEHwa4TS3cOg1R489s3Xvut4zueXO/rJPeDSsm8sm8PKLEABfQNeZXaBguM+ZwAAAABJRU5ErkJggg==
"""
bb2 = b"""
iVBORw0KGgoAAAANSUhEUgAAAAsAAAAXCAIAAABS2iKRAAAACXBIWXMAABXXAAAV1wEELLsZAAABbklEQVQoz4WSvUoDQRSFz0wmKAajIkZcLMQiQtJZBuzs
fQYfw9cQa2sLW0G0kmhjgj+oEaNLYoLmb3cTQ9bdnezOWKyuiVniLS4XzseZc2eGbO+eWhAtAzZgIKTIa83o2bzasEq69qg6N4bZ+kO4rgtASukK0dLMs/vG
yVMlP+DGKKX+RClVFuNbc1PLiUlkiwHECCG+ByGEEBKbiGZSCoAAYt+HDXCMsUxK4Rzd42JRgg6FIsTvkUhkPZnYTC8Bw8QgNBOLbqTnk6NEwFFKEwvTymoY
EdjEY9E1lgj38CtK6YoyO44AQAn+ITwhxhGe56mddgghpfT7x6dbetPDPaSUnidqTaugjeQIDHrcOS9X639uLJA557lC4yin/76cr/mD4/B88X3/Uq1IAGCD
mpTStu2rl/peVr3Tf/6HEAKAkLLP+x3bungoH95qgQyAGe2OyZ1qs/usmvmaXdDN5nB2tnNwbUE0ddiAHrb5F4YPydfUz8N6AAAAAElFTkSuQmCC
"""



























#  base64 -w120 logo.png
logo = b"""
iVBORw0KGgoAAAANSUhEUgAAAF8AAAA8CAYAAAAALGYBAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAyQAAAMkBxro13wAAABl0RVh0U29mdHdhcmUAd3d3
Lmlua3NjYXBlLm9yZ5vuPBoAAAANdEVYdEF1dGhvcgBuYmNvcnDzKdAeAAAAOXRFWHREZXNjcmlwdGlvbgBEb3VnaCBhbmQgcm9sbGluZyBwaW4sIHNpbXBs
ZSBkZXNpZ24uIFl1bW15ICEKnWXDAAAAIXRFWHRDcmVhdGlvbiBUaW1lADIwMTEtMTAtMjdUMTE6MzA6NDGL6CHJAAAAPHRFWHRTb3VyY2UAaHR0cHM6Ly9v
cGVuY2xpcGFydC5vcmcvZGV0YWlsLzE2NDkyOS9kb3VnaC1ieS1uYmNvcnDbKtIwAAAAWHRFWHRDb3B5cmlnaHQAQ0MwIFB1YmxpYyBEb21haW4gRGVkaWNh