diff --git a/CHANGELOG.md b/CHANGELOG.md index 9422f39..34cf5eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +# 0.2.2 - 2022-02-27 +* support using different orientation # 0.2.1 - 2022-02-27 * disable a group if all its skills are new or not specified # 0.2.0 - 2022-02-27 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a5d7f10 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2012-2022 Scott Chacon and others + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..59f67de --- /dev/null +++ b/README.md @@ -0,0 +1,207 @@ +# skillmap +A tool for generating skill map/tree like diagram. + +# What is a skill map/tree? +Skill tree is a term used in video games, and it can be used for describing roadmaps for software project development as well. + +This project borrows inspiration and ideas from two sources: +1. https://hacks.mozilla.org/2018/10/webassemblys-post-mvp-future/ +2. https://github.com/nikomatsakis/skill-tree + +# Installation +``` +pip install skillmap +``` +After installation, a `skillmap` command is available. + +# Usage +1. Create a toml format skill map descriptor file. You can find more details about this descriptor format [here](docs/skillmap_descriptor.md). For a minimal example, see [`docs/examples/hello_world.toml`](docs/examples/hello_world.toml) +``` +[skillmap] +name = "hello world" +icon = "bicycle" + +[groups.learn_python] +name = "learn python" +icon = "rocket" + [groups.learn_python.skills.print] + name = "print statement" + icon = "printer" + [groups.learn_python.skills.string] + name = "string literal" + icon = "book" +``` + +2. Run `skillmap path/to/your/skillmap.toml` + 1. For example, `skillmap docs/examples/hello_world.toml` +3. Copy the generated skill map diagram to your clipboard. +4. Paste the diagram to a mermaid diagram editor, for example, [`https://mermaid-js.github.io/mermaid-live-editor`](https://mermaid-js.github.io/mermaid-live-editor). + +# Examples +```mermaid +flowchart TD +url_shortener(fa:fa-hashtag
url shortener) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +subgraph groups.webui[fa:fa-desktop web ui] +groups.webui.skills.url_validator(fa:fa-globe
url validator) +class groups.webui.skills.url_validator newSkill; +groups.webui.skills.react-->groups.webui.skills.url_validator +groups.webui.skills.react(fa:fa-list
react) +class groups.webui.skills.react beingLearnedSkill; + +end +class groups.webui normalSkillGroup; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +subgraph groups.native_client[fa:fa-desktop native client] +groups.native_client.skills.react_native(fa:fa-mobile
react native) +class groups.native_client.skills.react_native newSkill; + +groups.native_client.skills.other_clients(fa:fa-lock
???) +class groups.native_client.skills.other_clients unknownSkill; + +end +class groups.native_client newSkillGroup; +groups.webui-->groups.native_client + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +subgraph groups.backend[fa:fa-server backend] +groups.backend.skills.restapi(fa:fa-send
REST API) +class groups.backend.skills.restapi learnedSkill; + +groups.backend.skills.database(fa:fa-database
database) +class groups.backend.skills.database learnedSkill; + +end +class groups.backend normalSkillGroup; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +subgraph groups.partitioned_backend[fa:fa-copy partitioned backend] +groups.partitioned_backend.skills.proxy(fa:fa-anchor
proxy) +class groups.partitioned_backend.skills.proxy beingLearnedSkill; + +groups.partitioned_backend.skills.database(fa:fa-database
partitioned database) +class groups.partitioned_backend.skills.database beingLearnedSkill; + +end +class groups.partitioned_backend normalSkillGroup; +groups.backend-->groups.partitioned_backend + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +subgraph groups.cache[ cache] +groups.cache.skills.memcache(fa:fa-magnet
memcache) +class groups.cache.skills.memcache learnedSkill; + +groups.cache.skills.redis(fa:fa-lock
???) +class groups.cache.skills.redis unknownSkill; + +end +class groups.cache normalSkillGroup; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +url_shortener-->groups.webui +url_shortener-->groups.backend +url_shortener-->groups.cache +classDef normalSkillGroup stroke:#0096C7,stroke-width:4px,fill:#CAF0F8; + +classDef beingLearnedSkill stroke-width:2px,stroke:#90E0EF,fill:#ADE8F4; +classDef learnedSkill stroke-width:2px,stroke:#00B4D8,fill:#48CAE4; + +classDef newSkillGroup stroke-width:4px,stroke:#D6CCC2,fill:#EDEDE9; +classDef newSkill stroke-width:2px,stroke:#D6CCC2,fill:#EDEDE9; +classDef unknownSkill stroke-width:2px,stroke:#D6CCC2,fill:#EDEDE9; + +class url_shortener normalSkillGroup; +``` + +```mermaid +flowchart TD +url_shortener(fa:fa-hashtag
url shortener) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +subgraph groups.webui[fa:fa-desktop web ui] +groups.webui.skills.url_validator(fa:fa-globe
url validator) +class groups.webui.skills.url_validator newSkill; +groups.webui.skills.react-->groups.webui.skills.url_validator +groups.webui.skills.react(fa:fa-list
react) +class groups.webui.skills.react beingLearnedSkill; + +end +class groups.webui normalSkillGroup; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +subgraph groups.native_client[fa:fa-desktop native client] +groups.native_client.skills.react_native(fa:fa-mobile
react native) +class groups.native_client.skills.react_native newSkill; + +groups.native_client.skills.other_clients(fa:fa-lock
???) +class groups.native_client.skills.other_clients unknownSkill; + +end +class groups.native_client newSkillGroup; +groups.webui-->groups.native_client + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +subgraph groups.backend[fa:fa-server backend] +groups.backend.skills.restapi(fa:fa-send
REST API) +class groups.backend.skills.restapi learnedSkill; + +groups.backend.skills.database(fa:fa-database
database) +class groups.backend.skills.database learnedSkill; + +end +class groups.backend normalSkillGroup; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +subgraph groups.partitioned_backend[fa:fa-copy partitioned backend] +groups.partitioned_backend.skills.proxy(fa:fa-anchor
proxy) +class groups.partitioned_backend.skills.proxy beingLearnedSkill; + +groups.partitioned_backend.skills.database(fa:fa-database
partitioned database) +class groups.partitioned_backend.skills.database beingLearnedSkill; + +end +class groups.partitioned_backend normalSkillGroup; +groups.backend-->groups.partitioned_backend + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +subgraph groups.cache[ cache] +groups.cache.skills.memcache(fa:fa-magnet
memcache) +class groups.cache.skills.memcache learnedSkill; + +groups.cache.skills.redis(fa:fa-lock
???) +class groups.cache.skills.redis unknownSkill; + +end +class groups.cache normalSkillGroup; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +url_shortener-->groups.webui +url_shortener-->groups.backend +url_shortener-->groups.cache +classDef normalSkillGroup stroke-width:4px,stroke:#DCEBCA,fill:#E9F5DB; +classDef beingLearnedSkill stroke-width:2px,stroke:#C2D5AA,fill:#CFE1B9; +classDef learnedSkill stroke-width:2px,stroke:#A6B98B,fill:#B5C99A; + +classDef newSkillGroup stroke-width:4px,stroke:#D6CCC2,fill:#EDEDE9; +classDef newSkill stroke-width:2px,stroke:#D6CCC2,fill:#EDEDE9; +classDef unknownSkill stroke-width:2px,stroke:#D6CCC2,fill:#EDEDE9; + +%% https://www.w3schools.com/colors/colors_groups.asp +linkStyle default stroke-width:2px,stroke:OliveDrab; +class url_shortener normalSkillGroup; +``` + +# License +[MIT License](LICENSE) + +# More details +* Skillmap toml descriptor format can be found [here](docs/skillmap_descriptor.md) +* hot reloading when authoring a skillmap toml file + * install several tools to make hot reloading to work + * [`entr`](https://github.com/eradman/entr), run arbitrary commands when files change + * [Visual Studio Code](https://code.visualstudio.com) + [Markdown Preview Enhanced Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced) + * Basically, use `entr` to watch toml file changes, and generate a `md` makrdown file using `skillmap` every time when toml file changes. And use `vscode` + `Markdown Preview Enhanced` extension to open this generated markdown file. Check out `build_sample` and `dev_sample` in [justfile](justfile) to see how to make hot reloading work \ No newline at end of file diff --git a/README.rst b/README.rst deleted file mode 100644 index e69de29..0000000 diff --git a/docs/examples/hello_world.toml b/docs/examples/hello_world.toml new file mode 100644 index 0000000..9aebbdc --- /dev/null +++ b/docs/examples/hello_world.toml @@ -0,0 +1,13 @@ +[skillmap] +name = "hello world" +icon = "bicycle" + +[groups.learn_python] +name = "learn python" +icon = "rocket" + [groups.learn_python.skills.print] + name = "print statement" + icon = "printer" + [groups.learn_python.skills.string] + name = "string literal" + icon = "book" diff --git a/docs/skillmap_descriptor.md b/docs/skillmap_descriptor.md new file mode 100644 index 0000000..ea716f3 --- /dev/null +++ b/docs/skillmap_descriptor.md @@ -0,0 +1,52 @@ +# Skillmap descriptor format +* The skillmap descriptor format is a toml file. Here is a minimal example, see [`examples/hello_world.toml`](examples/hello_world.toml) +* It describes three concepts in the file: + * `skillmap`: the root key of the toml file. Typically, it is used to represent a project. + * `group`: a group of skills. In each file, it can have multiple groups in it. You can use `group` to represent sub project/component in a project. + * `skill`: a specific skill. Each group can have multiple skills in it. You can use `skill` to represent a specific component/module in a sub project/component. +### skillmap toml table +* The `skillmap` toml table can have some fields: + * `name`: [optional] the name of the skillmap. It will be used as a label in the diagram for the top level node. + * `icon`: [optional] a fontawsome icon name. It will be used as an icon in the diagram. You can find the fontawsome icon list [here](https://fontawesome.com/v4.7.0/icons/). + * `theme`: [optional] theme for the diagram. Serveral themes are included: + * ocean (default theme) + * earth + * grape + * grass + * pale + * rose + * `orientation`: [optional] the orientation of the diagram. [All mermaid's orientations](https://mermaid-js.github.io/mermaid/#/flowchart?id=flowchart-orientation) are supported, including: + * TB - top to bottom (default) + * TD - top-down/ same as top to bottom + * BT - bottom to top + * RL - right to left + * LR - left to right +## group/skill toml tables +* The `group`/`skill` toml table can have some fields: + * `name`: [optional] the name of the skillmap/group/skill. It will be used as a label in the diagram. . + * `icon`: [optional] a fontawsome icon name. It will be used as an icon in the diagram. You can find the fontawsome icon list [here](https://fontawesome.com/v4.7.0/icons/). + * `requires`: [optional] a list of strings. It indicates a list of skill groups or skills to be learned before this learning this skill group/skill. where each string is a toml table name of a group/skill. It will be rendered as an edge(s) from one node to another. + +## Example +```toml +[groups.learn_python] +name = "learn python" +icon = "rocket" + [groups.learn_python.skills.print] + name = "print statement" + icon = "printer" + [groups.learn_python.skills.string] + name = "string literal" + icon = "book" + +[groups.program_with_python] +name = "program with python" +icon = "car" +requires = ["groups.learn_python"] +``` +In this exmaple, there are: +* two groups: `groups.learn_python` and `groups.program_with_python` +* `groups.learn_python` has two skills: + * `groups.learn_python.skills.print` + * `groups.learn_python.skills.string` +* `groups.program_with_python` requires `groups.learn_python` to be learned first. When drawn in the diagram, it will be rendered as an edge from `groups.learn_python` to `groups.program_with_python`. \ No newline at end of file diff --git a/justfile b/justfile index 2e5391f..69bd079 100644 --- a/justfile +++ b/justfile @@ -5,3 +5,9 @@ setup: # install the library into system python rm -fr ./dist poetry build && pip install ./dist/skillmap-*-py3-none-any.whl --force-reinstall + +build_sample src="tests/url_shortener.toml" dest="dist/url_shortener.md": + echo '```mermaid' > {{ dest }} && skillmap {{ src }} >> {{ dest }} && echo '```' >> {{ dest }} + +dev_sample src="tests": + find {{ src }} -iname "*.toml" | entr -s "just build_sample" \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index b7c531b..ce7d424 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "skillmap" -version = "0.1.0" +version = "0.2.2" description = "Skillmap generates a skill tree from a toml file" authors = ["Yue Ni "] diff --git a/skillmap/main.py b/skillmap/main.py index ae6e7b2..f8222ad 100644 --- a/skillmap/main.py +++ b/skillmap/main.py @@ -20,10 +20,10 @@ def _skillmap_parser(): parser = argparse.ArgumentParser(prog="skillmap") parser.add_argument( - "skillmap", + "descriptor_toml", default=False, type=str, - help="Skillmap descriptor toml file", + help="The path to a toml file describing the skillmap", ) parser.add_argument( "--version", @@ -32,13 +32,13 @@ def _skillmap_parser(): help="show version number", ) - parser.add_argument( - "-f", - "--format", - type=str, - default="mermaid", - help="export format, [mermaid] are supported", - ) + # parser.add_argument( + # "-f", + # "--format", + # type=str, + # default="mermaid", + # help="export format, [mermaid] are supported", + # ) return parser @@ -56,7 +56,7 @@ def generate(skillmap_file, format = None): def main(): args = parse_sys_args(sys.argv[1:]) - skillmap_file = Path(args["skillmap"]) - format = args["format"] + skillmap_file = Path(args["descriptor_toml"]) + # format = args["format"] skillmap_graph = generate(skillmap_file, format) print(skillmap_graph) diff --git a/skillmap/skillmap_visitor.py b/skillmap/skillmap_visitor.py index f0987a1..78b6780 100644 --- a/skillmap/skillmap_visitor.py +++ b/skillmap/skillmap_visitor.py @@ -1,8 +1,11 @@ -import os - +from skillmap.theme_loader import load_theme +import re SECTION_SEPARATOR = "%" * 30 +def alphanumerize(s): + return re.sub(r'\W+', "_", s) + def get_icon(dict_value): icon = "" @@ -11,14 +14,6 @@ def get_icon(dict_value): return icon -def theme_css_styles(theme): - theme_file = f"skillmap/themes/{theme}.theme" - if not os.path.exists(theme_file): - theme_file = f"skillmap/themes/ocean.theme" - with open(theme_file, "r") as f: - return f.read() - - def _qualify(group_id): return f"groups.{group_id}" @@ -27,9 +22,13 @@ def _qualified_skill_id(qualified_group_id, skill_id): return f"{qualified_group_id}.skills.{skill_id}" -def groups_edges(map_name, groups): - group_ids = [_qualify(group_id) for group_id, _ in groups.items()] - groups_edges = "\n".join([f"{map_name}-->{gid}" for gid in group_ids]) +def groups_edges(map_id, groups): + group_ids = [ + _qualify(group_id) + for group_id, group_value in groups.items() + if "requires" not in group_value # all groups without requires + ] + groups_edges = "\n".join([f"{map_id}-->{gid}" for gid in group_ids]) return groups_edges @@ -112,25 +111,34 @@ def group_subgraphs(groups): ] return "\n\n".join(group_graphs) +def get_orientation(skill_map_dict): + orientation = skill_map_dict.get("orientation", "TD") + if orientation not in ["TD", "TB", "BT", "RL", "LR"]: + orientation = "TD" + return orientation + def skill_map_graph(skill_map): skill_map_dict = skill_map.get("skillmap", {}) map_name = skill_map_dict.get("name", "") + map_id = alphanumerize(map_name) theme = skill_map_dict.get("theme", "ocean") + orientation = get_orientation(skill_map_dict) map_icon = get_icon(skill_map_dict) - map_to_group_edges = groups_edges(map_name, skill_map.get("groups", {})) + map_to_group_edges = groups_edges(map_id, skill_map.get("groups", {})) map_group_subgraphs = group_subgraphs(skill_map.get("groups", {})) - skill_map_node = f"{map_name}({map_icon}
{map_name})" - skill_map_node_style = f"class {map_name} normalSkillGroup;" + skill_map_node = f"{map_id}({map_icon}
{map_name})" + skill_map_node_style = f"class {map_id} normalSkillGroup;" + skill_map_header = f"flowchart {orientation}" sections = [ - "flowchart TD", + skill_map_header, skill_map_node, map_group_subgraphs, SECTION_SEPARATOR, map_to_group_edges, - theme_css_styles(theme), + load_theme(theme), skill_map_node_style, ] return "\n".join(sections) diff --git a/skillmap/theme_loader.py b/skillmap/theme_loader.py new file mode 100644 index 0000000..217dd71 --- /dev/null +++ b/skillmap/theme_loader.py @@ -0,0 +1,12 @@ +import pkgutil +import os + +def load_theme(theme): + for try_theme in [theme, "ocean"]: + theme_file = f"themes/{try_theme}.theme" + try: + theme_data = pkgutil.get_data(__name__, theme_file) + if theme_data: + return theme_data.decode("utf-8") + except FileNotFoundError: + pass \ No newline at end of file diff --git a/tests/skillmap_parser_test.py b/tests/skillmap_parser_test.py index d27dcf2..c0a4975 100644 --- a/tests/skillmap_parser_test.py +++ b/tests/skillmap_parser_test.py @@ -3,9 +3,9 @@ def test_parse_toml(): parser = SkillMapParser() - skill_map = parser.parse('tests/urlshortener.toml') + skill_map = parser.parse('tests/url_shortener.toml') assert skill_map - assert skill_map['skillmap']['name'] == "urlshortener" + assert skill_map['skillmap']['name'] == "url shortener" assert skill_map['groups']['webui']['name'] == "web ui" assert skill_map['groups']['webui']['skills']['url_validator']['name'] == "url validator" assert skill_map['groups']['webui']['skills']['url_validator']['icon'] == "globe" diff --git a/tests/skillmap_visitor_test.py b/tests/skillmap_visitor_test.py index 2fc55b9..884e1bc 100644 --- a/tests/skillmap_visitor_test.py +++ b/tests/skillmap_visitor_test.py @@ -8,15 +8,69 @@ def test_generate(): - skillmap_file = "tests/urlshortener.toml" + skillmap_file = "tests/url_shortener.toml" skillmap_graph = generate(skillmap_file) assert skillmap_graph assert "flowchart TD" in skillmap_graph - assert "urlshortener" in skillmap_graph - assert "urlshortener-->groups.webui" in skillmap_graph + assert "url_shortener" in skillmap_graph + assert "url_shortener-->groups.backend" in skillmap_graph assert "class groups.webui" in skillmap_graph +def test_skillmap_node_with_missing_theme(): + visitor = SkillMapVisitor() + skillmap_graph = visitor.visit( + { + "skillmap": { + "name": "url shortener", + "icon": "anchor", + "theme": "not_found", + } + } + ) + assert skillmap_graph + assert "flowchart TD" in skillmap_graph + assert "url shortener" in skillmap_graph + +def test_skillmap_node_with_orientation(): + visitor = SkillMapVisitor() + skillmap_graph = visitor.visit( + { + "skillmap": { + "name": "url shortener", + "icon": "anchor", + "orientation": "LR", + } + } + ) + assert skillmap_graph + assert "flowchart LR" in skillmap_graph + assert "url shortener" in skillmap_graph + +def test_skillmap_node_with_auto_required_groups(): + visitor = SkillMapVisitor() + skillmap_graph = visitor.visit( + { + "skillmap": { + "name": "url shortener", + }, + "groups": { + "g1": { + "name": "g1", + }, + "g2": { + "name": "g2", + "requires": ["g1"], + }, + }, + } + ) + assert skillmap_graph + assert "flowchart TD" in skillmap_graph + assert "url_shortener-->groups.g1" in skillmap_graph + assert "url_shortener-->groups.g2" not in skillmap_graph + + def test_skill_node(): skill_graph = skill_node("s1", {"name": "url validator", "icon": "globe"}) sections = ["s1(fa:fa-globe
url validator)", "class s1 newSkill;", ""] @@ -30,16 +84,21 @@ def test_skill_node_with_status(): sections = ["s1(fa:fa-globe
url validator)", "class s1 beingLearnedSkill;", ""] assert skill_graph.split("\n") == sections + def test_locked_skill_node(): skill_graph = skill_node("s1", {}) sections = ["s1(fa:fa-lock
???)", "class s1 unknownSkill;", ""] assert skill_graph.split("\n") == sections + def test_skill_node_with_requires(): - skill_graph = skill_node("s1", {"name": "url validator", "icon": "globe", "requires": ["s2"]}) + skill_graph = skill_node( + "s1", {"name": "url validator", "icon": "globe", "requires": ["s2"]} + ) sections = ["s1(fa:fa-globe
url validator)", "class s1 newSkill;", "s2-->s1"] assert skill_graph.split("\n") == sections + def test_visit_group_without_skill(): group_graph = group_subgraph( "g1", @@ -54,7 +113,7 @@ def test_visit_group_without_skill(): "", # skill list is skipped "end", "class groups.g1 newSkillGroup;", - "" + "", ] assert group_graph.split("\n") == sections @@ -82,10 +141,11 @@ def test_visit_group(): "", "end", "class groups.g1 newSkillGroup;", - "" + "", ] assert group_graph.split("\n") == sections + def test_visit_group_with_requires(): group_graph = group_subgraph( "g1", diff --git a/tests/url_shortener.toml b/tests/url_shortener.toml new file mode 100644 index 0000000..136d71e --- /dev/null +++ b/tests/url_shortener.toml @@ -0,0 +1,58 @@ +[skillmap] +name = "url shortener" +icon = "hashtag" +theme = "pale" + +[groups.webui] +name = "web ui" +icon = "desktop" + [groups.webui.skills.url_validator] + name = "url validator" + icon = "globe" + requires = ["groups.webui.skills.react"] + [groups.webui.skills.react] + name = "react" + icon = "list" + status = "beingLearned" + +[groups.native_client] +name = "native client" +icon = "desktop" +requires = ["groups.webui"] + [groups.native_client.skills.react_native] + name = "react native" + icon = "mobile" + [groups.native_client.skills.other_clients] + +[groups.backend] +name = "backend" +icon = "server" + [groups.backend.skills.restapi] + name = "REST API" + icon = "send" + status = "learned" + [groups.backend.skills.database] + name = "database" + icon = "database" + status = "learned" + +[groups.partitioned_backend] +name = "partitioned backend" +icon = "copy" +requires = ["groups.backend"] + [groups.partitioned_backend.skills.proxy] + name = "proxy" + icon = "anchor" + status = "beingLearned" + [groups.partitioned_backend.skills.database] + name = "partitioned database" + icon = "database" + status = "beingLearned" + +[groups.cache] +name = "cache" + [groups.cache.skills.memcache] + name = "memcache" + icon = "magnet" + status = "learned" + [groups.cache.skills.redis] \ No newline at end of file diff --git a/tests/urlshortener.toml b/tests/urlshortener.toml deleted file mode 100644 index 9f175ca..0000000 --- a/tests/urlshortener.toml +++ /dev/null @@ -1,30 +0,0 @@ -[skillmap] -name = "urlshortener" - -[groups.webui] -name = "web ui" -icon = "anchor" -requires = ["groups.backend.skills.restapi"] - [groups.webui.skills.url_validator] - name = "url validator" - icon = "globe" - requires = ["groups.webui.skills.react"] - [groups.webui.skills.react] - name = "react" - icon = "list" - status = "beingLearned" - -[groups.backend] -name = "backend" - [groups.backend.skills.restapi] - name = "REST API" - icon = "server" - [groups.backend.skills.database] - name = "database" - icon = "database" - status = "learned" - -[groups.cache] -name = "cache" - [groups.cache.skills.memcached] - [groups.cache.skills.redis] \ No newline at end of file