Check-in [01853f4400]
Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Enable replay option, add verbose setting, use full pluginconf config[] list structure now (select: and value: just the default), keep all _control and __private vars around; but omit them from default context for invocation. Add startup progressbar. |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
01853f4400d947354d6b9c80099ca25e |
User & Date: | mario 2021-03-24 09:08:27 |
Context
2021-03-24
| ||
09:13 | Minor updates to help/ check-in: 41f2f2556e user: mario tags: trunk | |
09:08 | Enable replay option, add verbose setting, use full pluginconf config[] list structure now (select: and value: just the default), keep all _control and __private vars around; but omit them from default context for invocation. Add startup progressbar. check-in: 01853f4400 user: mario tags: trunk | |
2021-03-23
| ||
15:26 | serialize JSON without Unicode escapes, release as 0.1.0 check-in: 8a032a1e3c user: mario tags: trunk, 0.1.0 | |
Changes
Changes to cookiedough/__init__.py.
1 2 3 4 5 6 7 8 | #!/usr/bin/env python3 # encoding: utf-8 # fmt: off # api: python # type: gui # title: cookiedough # description: browser and install GUI for cookiecutter templates # category: viewer | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/usr/bin/env python3 # encoding: utf-8 # fmt: off # api: python # type: gui # title: cookiedough # description: browser and install GUI for cookiecutter templates # category: viewer # version: 0.1.5 # state: alpha # license: proprietary # config: # { name: colorize, type: bool, value: 1, description: Colorize the README preview, help: Basically just highlighting of headlines and code blocks. Display is marginally faster if disabled. } # { name: sort, type: select, value: all, select: all|size|stars|forks|name|short|vars|files|updated_at, description: Primary sorting property, help: Uses internal scores, or properties like the Β»shortΒ« name. } # { name: show_counts, type: bool, value: 1, description: Show number of entries per language/api category, help: Else just the names. } # { name: search_keypress, type: bool, value: 0, description: Search on any keypress - instead of just Enter., help: Requires restarting cookiedough. } |
︙ | ︙ | |||
62 63 64 65 66 67 68 | "sort": "all", "show_counts": True, "search_keypress": False, "no_params": False, "update_ccjson": True, "hook_prompt": True, "editor": "mousepad", | | > > > > | 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | "sort": "all", "show_counts": True, "search_keypress": False, "no_params": False, "update_ccjson": True, "hook_prompt": True, "editor": "mousepad", "replay": True, "verbose": False, "theme": "DarkBlue2", "conf_file": appdirs.user_config_dir("cookiedough", "io") + "/settings.json", "plugins": { "__init__": 1, "rollout": 1, "icons": 1 } } try: conf.update(json.load(open(conf['conf_file'], "r", encoding="utf-8"))) except: pass conf["debug"] = "--debug" in sys.argv rollout.conf = conf #-- JSON blob of templates class repos(): def __init__(self): update.progress(5) # load data file fn = re.sub("[\w.]+$", "uidata.json", __file__) self.ls = json.load(open(fn, "r", encoding="utf-8")) update.progress(20) # prepare sorting [update.score(d) for d in self.ls.values()] update.progress(50) def tree(self, ls=None): """ Convert to tree list """ if not ls: ls = self.ls if isinstance(ls, dict): ls = ls.values() |
︙ | ︙ | |||
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 | #log.init.info("build window") sg.theme(conf["theme"]) self.w = sg.Window( title=f"cookiedough", layout=layout, font="Sans 12", size=(1080,725), margins=(0,0), resizable=False, use_custom_titlebar=False, background_color="#fafafa", icon=icons.icon#, ttk_theme="yaru" ) self.win_map = {} # alias functions self.status = self.w["status"].update # widget patching per tk 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() self.win_map[win] = cb win.read(timeout=1) # demultiplex PySimpleGUI events across multiple windows def main(self): self.win_register(self.w, self.event) while True: win_ls = [win for win in self.win_map.keys()] #log.event_loop.win_ls_length.debug(len(win_ls)) # unlink closed windows for win in win_ls: if win.TKrootDestroyed: | > > > | 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | #log.init.info("build window") sg.theme(conf["theme"]) self.w = sg.Window( title=f"cookiedough", layout=layout, font="Sans 12", size=(1080,725), margins=(0,0), resizable=False, use_custom_titlebar=False, background_color="#fafafa", icon=icons.icon#, ttk_theme="yaru" ) update.progress(65) self.win_map = {} # alias functions self.status = self.w["status"].update # widget patching per tk self.w.read(timeout=1) update.progress(90) 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() self.win_map[win] = cb win.read(timeout=1) # demultiplex PySimpleGUI events across multiple windows def main(self): update.progress(100) self.win_register(self.w, self.event) while True: win_ls = [win for win in self.win_map.keys()] #log.event_loop.win_ls_length.debug(len(win_ls)) # unlink closed windows for win in win_ls: if win.TKrootDestroyed: |
︙ | ︙ |
Changes to cookiedough/rollout.py.
1 2 3 4 | # encoding: utf-8 # api: cookiedough # type: function # title: roll out | | | | | | | | > > > > > > > > > > > > > < | 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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | # encoding: utf-8 # api: cookiedough # type: function # title: roll out # description: deploy selected cookiecutter template # category: action # version: 0.5 # config: # { name: replay, type: bool, value: 1, description: Use replay/ variables as defaults, help: Overrides input variables with previous inputs for the same template. } # { name: update_ccjson, type: bool, value: 1, description: Update parameters from cookiecutter.json files, help: avoids extra prompts if template infos outdated } # { name: hook_prompt, type: bool, value: 1, description: Display any additional prompts as GUI inputs, help: hook prompt.* functions } # { name: no_params, type: bool, value: 0, description: Don't prompt for template vars. Use terminal prompts instead., help: You might as well use cookiecutter directly then. } # { name: verbose, type: bool, value: 0, description: cookiecutter --verbose for more details on extraction, help: Will print any output to console } # priority: core # depends: python:cookiecutter # # Implements the parameter input window before invoking cookiecutter(1) # for extracting a template. Also hooks cookiecutter.prompt.* functions, # to avoid any extra terminal prompts. # # Additionally updates config[] dict from remote cookiecutter.json defaults # and previous ~/.config/cookiecutter/replay/*.json input. # # Also contains the patch logic to modify cookiecutter to use ~/.config # per default. # import sys, os, re, json import PySimpleGUI as sg import requests, appdirs from cookiedough import icons from traceback import format_exc from textwrap import dedent # joined to main.conf conf = { "no_params": False, "update_ccjson": True, "hook_prompt": True, "replay": True, "verbose": False, } # finally, this is where cookiecutter gets invoked, # params={} from the input window # doc: https://cookiecutter.readthedocs.io/en/1.7.2/advanced/calling_from_python.html def cutting(repo_url, params): import cookiecutter.main # params ccc = CookieCutterConfig() if m := re.match("^(.+)\?(?:d|dir|directory)=(.+)$", repo_url): repo_url, directory = m.groups() # from http://repo.git/?dir=template2/ else: directory = None if params: no_input = True else: no_input = False # from conf[no_params], set by task.__init__ # inject verbose flag (alternatively: use cookiercutter.cli instead of .main) if conf.get("verbose"): import cookiecutter.log cookiecutter.log.configure_logger(stream_level='DEBUG', debug_file=None) # run dir = cookiecutter.main.cookiecutter( template=repo_url, #checkout=None, no_input=no_input, extra_context=params, #replay=None, #overwrite_if_exists=False, |
︙ | ︙ | |||
130 131 132 133 134 135 136 | # scan local ~/.config/cookiecutter/replay/* dir, get override values from there def update_replay(d): replay = {} try: m = re.search("(/[\w\-]+)(\.git|\.zip)?(\?.+$|$)", d["repo"]) fn = CookieCutterConfig().replay + m[1] + ".json" | < | 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | # scan local ~/.config/cookiecutter/replay/* dir, get override values from there def update_replay(d): replay = {} try: m = re.search("(/[\w\-]+)(\.git|\.zip)?(\?.+$|$)", d["repo"]) fn = CookieCutterConfig().replay + m[1] + ".json" with open(fn, "r", encoding="utf-8") as f: replay = json.load(f)["cookiecutter"] # we could check if _template matches up with d[repo] at this point (likely couldn't account for ?directory=) except: return for e in d["config"]: if e["type"] not in ("str", "int", ): |
︙ | ︙ | |||
182 183 184 185 186 187 188 189 190 | # convert config[] list to input widgets def fields(self, cfg): bg = { "background_color": "#2980b9" } pad = { "background_color": "#c6d7e3", "pad": ((20,2),(0)) } ls = [] for e in cfg: ls.append(sg.Text(e["name"], font="Sans 12 bold", pad=((10,13),(10,2)), **bg)) if e["type"] == "select": values = e.get("select") or e.get("value") or [""] | > | | < < < < < > > > > > > > > | > | | | 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | # convert config[] list to input widgets def fields(self, cfg): bg = { "background_color": "#2980b9" } pad = { "background_color": "#c6d7e3", "pad": ((20,2),(0)) } ls = [] for e in cfg: ls.append(sg.Text(e["name"], font="Sans 12 bold", pad=((10,13),(10,2)), **bg)) _disabled = e["class"] != "cookiecutter" if e["type"] == "select": values = e.get("select") or e.get("value") or [""] ls.append(sg.Combo(values, default_value=e["value"], key=e["name"], font="Roboto 12", text_color="black", disabled=_disabled, **pad)) elif e["type"] == "dict": # display only, not editable, and cookiecutter will source it from the cc.json ls.append(sg.Multiline(json.dumps(e["value"], indent=2), size=(50,3), disabled=True, text_color="black", **pad)) else: ls.append(sg.Input(e["value"], key=e["name"], font="Roboto 11", text_color="black", disabled=_disabled, **pad)) if e.get("description"): ls.append(sg.Text(e["description"], font="Sans 9", text_color="#444", pad=(20,1), **bg)) ls.append(sg.T("", **bg)) return ls # window actions def event(self, event, data): if event == "#cancel": self.w.close() elif event == "#chdir": self.main.working_directory(...) elif event == "#bake": self.w.close() self.bake(data) # invoke cookiecutter from data{} widget values def bake(self, data): params = { # assemble all but _control and __private vars (cc will apply those itself) k:v for k,v in data.items() if re.match("^[a-z]+", k, re.I) } dir = cutting(self.d["repo"], params) if not conf.get("verbose"): print("cookiecutting done.") self.open_target(dir) self.main.status(f"{dir} created") # open extracted dir def open_target(self, dir): if os.path.exists(dir): os.system("xdg-open %r &" % dir) |
︙ | ︙ |
Changes to cookiedough/update.py.
︙ | ︙ | |||
8 9 10 11 12 13 14 | # # This is where the dev/ scripts might end up, so that GH/BB/GL could be # polled from the main window. For now it's just for the scoring algorithm. # (Basically looks for averages, some benefits for documentation quality.) # # | | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # # This is where the dev/ scripts might end up, so that GH/BB/GL could be # polled from the main window. For now it's just for the scoring algorithm. # (Basically looks for averages, some benefits for documentation quality.) # # import re, time, sys # local settings (not joined with main) conf = { "score.find": "Makefile | NEWS | CHANGES(?:\.md|\.rst)? | \.fpm(?:rc)?", "date": time.strftime("%Y-%m-%d"), } |
︙ | ︙ | |||
124 125 126 127 128 129 130 | 'luismartingil/cookiecutter-beamer', 'JonasGroeger/cookiecutter-mediawiki-extension', 'kkujawinski/cookiecutter-sublime-text-3-plugin', 'fhightower-templates/sublime-snippet-package-template', 'mahmoudimus/cookiecutter-slim-berkshelf-vagrant', 'audreyr/cookiecutter-complexity', 'keimlink/cookiecutter-reveal.js', 'relekang/cookiecutter-tumblr-theme', 'Plippe/cookiecutter-scala', 'jpzk/cookiecutter-scala-spark', 'joeyjoejoejr/cookiecutter-atari2600', 'jupyter-widgets/widget-cookiecutter', 'drivendata/cookiecutter-data-science', 'bdcaf/cookiecutter-r-data-analysis', 'docker-science/cookiecutter-docker-science', 'mkrapp/cookiecutter-reproducible-science', 'jastark/cookiecutter-data-driven-journalism', 'painless-software/painless-continuous-delivery', 'DualSpark/cookiecutter-tf-module', 'hkage/cookiecutter-tornado', 'Pawamoy/cookiecutter-awesome', 'sindresorhus/awesome', 'bdcaf/cookiecutter_dotfile', 'genzj/cookiecutter-raml'] return name in ids | > > > > > > > > > > | 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | 'luismartingil/cookiecutter-beamer', 'JonasGroeger/cookiecutter-mediawiki-extension', 'kkujawinski/cookiecutter-sublime-text-3-plugin', 'fhightower-templates/sublime-snippet-package-template', 'mahmoudimus/cookiecutter-slim-berkshelf-vagrant', 'audreyr/cookiecutter-complexity', 'keimlink/cookiecutter-reveal.js', 'relekang/cookiecutter-tumblr-theme', 'Plippe/cookiecutter-scala', 'jpzk/cookiecutter-scala-spark', 'joeyjoejoejr/cookiecutter-atari2600', 'jupyter-widgets/widget-cookiecutter', 'drivendata/cookiecutter-data-science', 'bdcaf/cookiecutter-r-data-analysis', 'docker-science/cookiecutter-docker-science', 'mkrapp/cookiecutter-reproducible-science', 'jastark/cookiecutter-data-driven-journalism', 'painless-software/painless-continuous-delivery', 'DualSpark/cookiecutter-tf-module', 'hkage/cookiecutter-tornado', 'Pawamoy/cookiecutter-awesome', 'sindresorhus/awesome', 'bdcaf/cookiecutter_dotfile', 'genzj/cookiecutter-raml'] return name in ids # progressbar from rich "β°β°β°β°β°β°β°β°β±β±β±β±β±" def progress(i=1, n=100, w=72): p = i/(n if n else 100) s = "β°" * int(round(p * w)) + "β±" * int(round(((1-p) * w))) if i >= n: print("\033[0K", end="") else: print("\0337" + s + "\0338", end="") sys.stdout.flush() |
Changes to dev/gh_conv.py.
︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 22 23 24 25 | f.write(json.dumps(results, indent=4, ensure_ascii=False)) def read(): with open("github.json", "r", encoding="utf-8") as f: return json.load(f) def tree2dir(tree): tree = [p["path"] for p in tree] tree = [re.sub("\{\{\s*cookiecutter\.(\w+)\s*\}\}", "{{$\\1}}", p) for p in tree] ls = [] pfx = [" ", " ", " ", " ", " ", " "] for p in tree.reverse() or tree: p = p.split("/") ind = len(p) | > > > > | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | f.write(json.dumps(results, indent=4, ensure_ascii=False)) def read(): with open("github.json", "r", encoding="utf-8") as f: return json.load(f) def tree2dir(tree): """ convert dir/file/struct into βββ βββ lists @todo: retain path names in ordered dict even? """ tree = [p["path"] for p in tree] tree = [re.sub("\{\{\s*cookiecutter\.(\w+)\s*\}\}", "{{$\\1}}", p) for p in tree] ls = [] pfx = [" ", " ", " ", " ", " ", " "] for p in tree.reverse() or tree: p = p.split("/") ind = len(p) |
︙ | ︙ | |||
48 49 50 51 52 53 54 55 | return r # extract from tables like """ | `project_slug` | Description of flag... | """ def readme2cfgdesc(readme): return dict(re.findall("^\s*\|\s* `?(\w+)`? \s*\|\s* (\w.+) \s*\|$", readme, re.M|re.X)) def ccjson2cfg(kv, desc): # invalid ex: souravsingh/cookiecutter-bear | > > > > | > > | > > | > > > > | > > > > > < < < > | > | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | return r # extract from tables like """ | `project_slug` | Description of flag... | """ def readme2cfgdesc(readme): return dict(re.findall("^\s*\|\s* `?(\w+)`? \s*\|\s* (\w.+) \s*\|$", readme, re.M|re.X)) def ccjson2cfg(kv, desc): """ transform key:value dict into pluginconf options structure, so we can differentiate types and add descriptions """ # invalid ex: souravsingh/cookiecutter-bear _special = ["_extensions", "_copy_without_render"] c = [] for k,v in kv.items(): _class, _type = "cookiecutter", "str" # class if k.startswith("__"): _class = "private" elif k.startswith("_"): _class = "control" # types if isinstance(v, list): c.append({ "name": k, "type": "select", "select": v, "value": v[0] if len(v) else "", "class": _class, "description": desc.get(k, ""), }) continue elif isinstance(v, dict): _type = "dict" elif v == None: _type = "str" v = "" elif isinstance(v, int): _type = "int" c.append({ "name": k, "type": _type, "value": v, "class": _class, "description": desc.get(k, ""), }) return c def lang2api(lang, name, ccjson_text): """ extract lang or api name from vnd/pkg name """ if not lang: lang = "other" api = re.sub("\s+", "-", lang.lower()) if m := re.search("(django|flask|node|wordpress|mediawiki)", api): api = m[1] if m := re.search('"_api":\s*"([\w\-]+)"', ccjson_text): api = m[1] |
︙ | ︙ |
Changes to setup.py.
︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 27 28 | name="cookiedough", long_description="README.md", packages=["cookiedough"], package_dir={"": "."}, package_data={ "cookiedough": [ "./*.json", "./help/*.*", ], }, include_package_data=True, entry_points={ "console_scripts": [ "cookiedough=cookiedough:main", | > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | name="cookiedough", long_description="README.md", packages=["cookiedough"], package_dir={"": "."}, package_data={ "cookiedough": [ "./*.json", "./*.patch", "./help/*.*", ], }, include_package_data=True, entry_points={ "console_scripts": [ "cookiedough=cookiedough:main", |
︙ | ︙ |