From 742e8fe15e466dc42dc46e6af00593a51d255f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Bratne?= Date: Fri, 20 Aug 2021 00:32:19 +0200 Subject: [PATCH 01/10] ls function split in two, made separate function findtopnode, to find the top node --- http_prompt/tree.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/http_prompt/tree.py b/http_prompt/tree.py index 345541f..2a2e9f9 100644 --- a/http_prompt/tree.py +++ b/http_prompt/tree.py @@ -57,7 +57,7 @@ def find_child(self, name, wildcard=True): return None - def ls(self, *path): + def findtopnode(self, path): success = True cur = self for name in path: @@ -74,5 +74,11 @@ def ls(self, *path): success = False break if success: - for node in sorted(cur.children): - yield node + return cur + + def ls(self, *path): + topnode = self.findtopnode(path) + if not topnode: + return + for node in sorted(topnode.children): + yield node From c161300832b5a07888c1dc64b3b71d7760eff2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Bratne?= Date: Fri, 20 Aug 2021 01:37:53 +0200 Subject: [PATCH 02/10] Fixed tree-function --- http_prompt/tree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/http_prompt/tree.py b/http_prompt/tree.py index 2a2e9f9..2bf3706 100644 --- a/http_prompt/tree.py +++ b/http_prompt/tree.py @@ -57,7 +57,7 @@ def find_child(self, name, wildcard=True): return None - def findtopnode(self, path): + def findtopnode(self, *path): success = True cur = self for name in path: @@ -77,7 +77,7 @@ def findtopnode(self, path): return cur def ls(self, *path): - topnode = self.findtopnode(path) + topnode = self.findtopnode(*path) if not topnode: return for node in sorted(topnode.children): From f3daf279b3e9c8794ee4f761104c1b79438fc992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Bratne?= Date: Fri, 20 Aug 2021 02:21:57 +0200 Subject: [PATCH 03/10] Some minor tweaking --- http_prompt/completer.py | 2 +- http_prompt/completion.py | 1 + http_prompt/execution.py | 43 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/http_prompt/completer.py b/http_prompt/completer.py index 8e97ae2..8c150f3 100644 --- a/http_prompt/completer.py +++ b/http_prompt/completer.py @@ -29,7 +29,7 @@ # '/foo/bar' => ('/foo/bar', 'bar') # '/foo/bar/' => ('/foo/bar/', '') # 'foo/bar' => ('foo/bar', 'bar') - (r'(ls|cd)\s+(/?(?:[^/]+/)*([^/]*)/?)$', 'urlpaths'), + (r'(ls|cd|tree)\s+(/?(?:[^/]+/)*([^/]*)/?)$', 'urlpaths'), (r'^\s*[^\s]*$', 'root_commands') ] diff --git a/http_prompt/completion.py b/http_prompt/completion.py index 598db30..ecba090 100644 --- a/http_prompt/completion.py +++ b/http_prompt/completion.py @@ -24,6 +24,7 @@ ('rm -q', 'Remove querystring parameter'), ('rm -q *', 'Remove all querystring parameters'), ('source', 'Load environment from a file'), + ('tree', 'Show tree structure'), ]) ACTIONS = OrderedDict([ diff --git a/http_prompt/execution.py b/http_prompt/execution.py index 9a66d7a..aa150cb 100644 --- a/http_prompt/execution.py +++ b/http_prompt/execution.py @@ -35,7 +35,7 @@ command = mutation / immutation mutation = concat_mut+ / nonconcat_mut - immutation = preview / action / ls / env / help / exit / exec / source / clear / _ + immutation = preview / action / ls / env / help / exit / exec / source / clear / tree /_ concat_mut = option_mut / full_quoted_mut / value_quoted_mut / unquoted_mut nonconcat_mut = cd / rm @@ -49,6 +49,7 @@ help = _ "help" _ exit = _ "exit" _ ls = _ "ls" _ (urlpath _)? (redir_out)? + tree = _ "tree" _ (urlpath _)? (redir_out)? env = _ "env" _ (redir_out)? source = _ "source" _ filepath _ exec = _ "exec" _ filepath _ @@ -351,6 +352,46 @@ def visit_ls(self, node, children): if lines: self.output.write('\n'.join(lines)) return node + + def _fetchtreenode(self, node, children, filtertypes=[], prepend=""): + #return [str(x) for x in range(10)] + ret = [] + ppsym = " " + + #cnodes = sorted([c for c in node.children if c.data.get("type") in filtertypes]) + cnodes = sorted([c for c in node.children], key=lambda x: x.data.get("type"), reverse=True) + + for i,c in enumerate(cnodes): + if i == len(cnodes)-1: + ppsym = "└── " + postsym = " " + else: + ppsym = "├── " + postsym = "│ " + if c.data.get("type") == "dir": + ret.append(prepend + ppsym + self._colorize(c.name, String)) + else: + ret.append(prepend + ppsym + self._colorize(c.name + " -> " + c.data.get("type"), Name)) + #token_type = String if c.data.get('type') == 'dir' else Name + ret += self._fetchtreenode(c, children, filtertypes, prepend + postsym) + + return ret + + def visit_tree(self, node, children): + path = urlparse(self.context_override.url).path + path = filter(None, path.split('/')) + topnode = self.context.root.findtopnode(*path) + lines = [] + if self.output.isatty(): + token_type = String if topnode.data.get('type') == 'dir' else Name + lines.append(self._colorize(topnode.name, token_type)) + lines += self._fetchtreenode(topnode, children, ["dir"]) + else: + #TODO: Test this + lines = [n.name for n in nodes] + if lines: + self.output.write('\n'.join(lines)) + return node def visit_env(self, node, children): text = format_to_http_prompt(self.context) From 3f81aabc03bf3cc7706079c4a457c17cdd7a1a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Bratne?= Date: Fri, 20 Aug 2021 04:29:02 +0200 Subject: [PATCH 04/10] Formatting correct for non-tty as well --- http_prompt/execution.py | 30 +++++++++++++++--------------- http_prompt/tree.py | 1 - 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/http_prompt/execution.py b/http_prompt/execution.py index aa150cb..13e6271 100644 --- a/http_prompt/execution.py +++ b/http_prompt/execution.py @@ -353,13 +353,22 @@ def visit_ls(self, node, children): self.output.write('\n'.join(lines)) return node - def _fetchtreenode(self, node, children, filtertypes=[], prepend=""): + def _formattreename(self, node, color=False): + if color: + col = Name + if node.data.get("type") == "dir": + col = String + return self._colorize(node.name, col) + else: + return node.name + + def _fetchtreenode(self, node, children, filtertypes=[], prepend="", colorize=True): #return [str(x) for x in range(10)] ret = [] ppsym = " " #cnodes = sorted([c for c in node.children if c.data.get("type") in filtertypes]) - cnodes = sorted([c for c in node.children], key=lambda x: x.data.get("type"), reverse=True) + cnodes = sorted(sorted([c for c in node.children if c.data.get("type") not in filtertypes], key=lambda x: x.name), key=lambda y: y.data.get("type"), reverse=True) for i,c in enumerate(cnodes): if i == len(cnodes)-1: @@ -368,12 +377,8 @@ def _fetchtreenode(self, node, children, filtertypes=[], prepend=""): else: ppsym = "├── " postsym = "│ " - if c.data.get("type") == "dir": - ret.append(prepend + ppsym + self._colorize(c.name, String)) - else: - ret.append(prepend + ppsym + self._colorize(c.name + " -> " + c.data.get("type"), Name)) - #token_type = String if c.data.get('type') == 'dir' else Name - ret += self._fetchtreenode(c, children, filtertypes, prepend + postsym) + ret.append(prepend + ppsym + self._formattreename(c, colorize)) + ret += self._fetchtreenode(c, children, filtertypes, prepend + postsym, colorize) return ret @@ -382,13 +387,8 @@ def visit_tree(self, node, children): path = filter(None, path.split('/')) topnode = self.context.root.findtopnode(*path) lines = [] - if self.output.isatty(): - token_type = String if topnode.data.get('type') == 'dir' else Name - lines.append(self._colorize(topnode.name, token_type)) - lines += self._fetchtreenode(topnode, children, ["dir"]) - else: - #TODO: Test this - lines = [n.name for n in nodes] + lines.append(self._formattreename(topnode,self.output.isatty())) + lines += self._fetchtreenode(topnode, children, ["file"], "", self.output.isatty()) if lines: self.output.write('\n'.join(lines)) return node diff --git a/http_prompt/tree.py b/http_prompt/tree.py index 2bf3706..a119bfa 100644 --- a/http_prompt/tree.py +++ b/http_prompt/tree.py @@ -1,6 +1,5 @@ """Tree data structure for ls command to work with OpenAPI specification.""" - class Node(object): def __init__(self, name, data=None, parent=None): From 8dc13fd5c5227a5a758711ec92d0c2b277d5dad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Bratne?= Date: Fri, 20 Aug 2021 04:42:33 +0200 Subject: [PATCH 05/10] Flake8 improvement --- http_prompt/execution.py | 44 ++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/http_prompt/execution.py b/http_prompt/execution.py index 13e6271..671aa5e 100644 --- a/http_prompt/execution.py +++ b/http_prompt/execution.py @@ -35,7 +35,8 @@ command = mutation / immutation mutation = concat_mut+ / nonconcat_mut - immutation = preview / action / ls / env / help / exit / exec / source / clear / tree /_ + immutation = preview / action / ls / env / help / + exit / exec / source / clear / tree /_ concat_mut = option_mut / full_quoted_mut / value_quoted_mut / unquoted_mut nonconcat_mut = cd / rm @@ -352,43 +353,52 @@ def visit_ls(self, node, children): if lines: self.output.write('\n'.join(lines)) return node - + def _formattreename(self, node, color=False): if color: col = Name - if node.data.get("type") == "dir": + if node.data.get("type") == "dir": col = String return self._colorize(node.name, col) else: return node.name - def _fetchtreenode(self, node, children, filtertypes=[], prepend="", colorize=True): - #return [str(x) for x in range(10)] + def _fetchtreenode(self, node, children, filtertypes=[], prepend="", + colorize=True): ret = [] ppsym = " " - - #cnodes = sorted([c for c in node.children if c.data.get("type") in filtertypes]) - cnodes = sorted(sorted([c for c in node.children if c.data.get("type") not in filtertypes], key=lambda x: x.name), key=lambda y: y.data.get("type"), reverse=True) - - for i,c in enumerate(cnodes): + + cnodes = sorted(sorted([c for c in node.children if c.data.get("type") + not in filtertypes], key=lambda x: x.name), + key=lambda y: y.data.get("type"), reverse=True) + + for i, c in enumerate(cnodes): if i == len(cnodes)-1: - ppsym = "└── " + ppsym = "└── " postsym = " " else: - ppsym = "├── " + ppsym = "├── " postsym = "│ " ret.append(prepend + ppsym + self._formattreename(c, colorize)) - ret += self._fetchtreenode(c, children, filtertypes, prepend + postsym, colorize) - + ret += self._fetchtreenode(c, + children, + filtertypes, + prepend + postsym, + colorize) + return ret - + def visit_tree(self, node, children): path = urlparse(self.context_override.url).path path = filter(None, path.split('/')) topnode = self.context.root.findtopnode(*path) lines = [] - lines.append(self._formattreename(topnode,self.output.isatty())) - lines += self._fetchtreenode(topnode, children, ["file"], "", self.output.isatty()) + lines.append(self._formattreename(topnode, self.output.isatty())) + lines += self._fetchtreenode(topnode, + children, + ["file"], + "", + self.output.isatty()) if lines: self.output.write('\n'.join(lines)) return node From e815c4ec6a326b5c263f60243ba7bcb574a87526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Bratne?= Date: Fri, 20 Aug 2021 04:46:00 +0200 Subject: [PATCH 06/10] Added newline to follow standards --- http_prompt/tree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/http_prompt/tree.py b/http_prompt/tree.py index a119bfa..2bf3706 100644 --- a/http_prompt/tree.py +++ b/http_prompt/tree.py @@ -1,5 +1,6 @@ """Tree data structure for ls command to work with OpenAPI specification.""" + class Node(object): def __init__(self, name, data=None, parent=None): From 014b8f0242a38b689b492c644be11cd9a4a0076c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Bratne?= Date: Fri, 20 Aug 2021 11:47:06 +0200 Subject: [PATCH 07/10] New successful tests addded --- tests/test_execution.py | 76 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/tests/test_execution.py b/tests/test_execution.py index ecb2063..fef09b6 100644 --- a/tests/test_execution.py +++ b/tests/test_execution.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- import hashlib -import io import json import shutil import os @@ -767,6 +766,81 @@ def test_reset(self): self.assertFalse(self.context.body_json_params) +class TestExecution_tree(ExecutionTestCase): + + def test_root(self): + execute('tree', self.context) + self.assert_stdout("root\n" + + "├── orgs\n" + + "│ └── {org}\n" + + "│ ├── events\n" + + "│ └── members\n" + + "└── users\n" + + " └── {username}\n" + + " ├── events\n" + + " └── orgs\n") + + def test_relative_path(self): + self.context.url = 'http://localhost/users' + execute('tree 101', self.context) + self.assert_stdout("{username}\n" + + "├── events\n" + + "└── orgs\n") + + def test_absolute_path(self): + self.context.url = 'http://localhost/users' + execute('tree /orgs/1', self.context) + self.assert_stdout("{org}\n" + + "├── events\n" + + "└── members\n") + + def test_redirect_write(self): + filename = self.make_tempfile() + + # Write something first to make sure it's a full overwrite + with open(filename, 'w') as f: + f.write('hello world\n') + + execute('tree > %s' % filename, self.context) + + with open(filename) as f: + content = f.read() + self.assertEqual(content, "root\n" + + "├── orgs\n" + + "│ └── {org}\n" + + "│ ├── events\n" + + "│ └── members\n" + + "└── users\n" + + " └── {username}\n" + + " ├── events\n" + + " └── orgs") + + def test_redirect_append(self): + filename = self.make_tempfile() + + # Write something first to make sure it's an append + with open(filename, 'w') as f: + f.write('hello world\n') + + execute('tree >> %s' % filename, self.context) + + with open(filename) as f: + content = f.read() + self.assertEqual(content, "hello world\nroot\n" + + "├── orgs\n" + + "│ └── {org}\n" + + "│ ├── events\n" + + "│ └── members\n" + + "└── users\n" + + " └── {username}\n" + + " ├── events\n" + + " └── orgs") + + def test_grep(self): + execute('tree | grep users', self.context) + self.assert_stdout('└── users\n') + + class TestExecution_ls(ExecutionTestCase): def test_root(self): From 30f1179b2f8a9e0c6bd61dc35297ed51c277c669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Bratne?= Date: Fri, 20 Aug 2021 11:50:51 +0200 Subject: [PATCH 08/10] Fixed flake error --- tests/test_cli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 73e7ef4..10d2517 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -186,7 +186,9 @@ def test_spec_basePath(self): self.assertEqual(lv3_names, set(['users', 'orgs'])) def test_spec_from_http(self): - spec_url = 'https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json' + spec_url = 'https://raw.githubusercontent.com/github/'\ + 'rest-api-description/main/descriptions/api.github.com/'\ + 'api.github.com.json' result, context = run_and_exit(['https://api.github.com', '--spec', spec_url]) self.assertEqual(result.exit_code, 0) From 20bf39bc3f2ab40d40699e5380f329339d2aaf56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Bratne?= Date: Fri, 20 Aug 2021 11:54:53 +0200 Subject: [PATCH 09/10] Fixed flake8 tests --- tests/context/test_context.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/context/test_context.py b/tests/context/test_context.py index 7c46f43..8d33389 100644 --- a/tests/context/test_context.py +++ b/tests/context/test_context.py @@ -149,13 +149,19 @@ def test_override(): orgs_methods = list(sorted(list(root_children)[0].children)) # path parameters are used even if no method parameter assert len(orgs_methods) == 2 - assert next(filter(lambda i:i.name == 'username', orgs_methods), None) is not None - assert next(filter(lambda i:i.name == 'Accept', orgs_methods), None) is not None + assert next(filter(lambda i: i.name == 'username', orgs_methods), None)\ + is not None + assert next(filter(lambda i: i.name == 'Accept', orgs_methods), None)\ + is not None users_methods = list(sorted(list(root_children)[1].children)) # path and methods parameters are merged assert len(users_methods) == 4 - assert next(filter(lambda i:i.name == 'username', users_methods), None) is not None - assert next(filter(lambda i:i.name == 'custom1', users_methods), None) is not None - assert next(filter(lambda i:i.name == 'custom2', users_methods), None) is not None - assert next(filter(lambda i:i.name == 'Accept', users_methods), None) is not None + assert next(filter(lambda i: i.name == 'username', users_methods), None)\ + is not None + assert next(filter(lambda i: i.name == 'custom1', users_methods), None)\ + is not None + assert next(filter(lambda i: i.name == 'custom2', users_methods), None)\ + is not None + assert next(filter(lambda i: i.name == 'Accept', users_methods), None)\ + is not None From 31d79225e13ebef08b78f1c1e799ac55ece3ce36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Bratne?= Date: Wed, 25 Aug 2021 17:26:38 +0200 Subject: [PATCH 10/10] Test defined with utf-8 when storing and retrieving file --- tests/test_execution.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_execution.py b/tests/test_execution.py index fef09b6..610229e 100644 --- a/tests/test_execution.py +++ b/tests/test_execution.py @@ -798,12 +798,12 @@ def test_redirect_write(self): filename = self.make_tempfile() # Write something first to make sure it's a full overwrite - with open(filename, 'w') as f: + with open(filename, 'w', encoding="utf-8") as f: f.write('hello world\n') execute('tree > %s' % filename, self.context) - with open(filename) as f: + with open(filename, encoding="utf-8") as f: content = f.read() self.assertEqual(content, "root\n" + "├── orgs\n" + @@ -819,12 +819,12 @@ def test_redirect_append(self): filename = self.make_tempfile() # Write something first to make sure it's an append - with open(filename, 'w') as f: + with open(filename, 'w', encoding="utf-8") as f: f.write('hello world\n') execute('tree >> %s' % filename, self.context) - with open(filename) as f: + with open(filename, encoding="utf-8") as f: content = f.read() self.assertEqual(content, "hello world\nroot\n" + "├── orgs\n" +