Skip to content

Commit 583734f

Browse files
committed
🚧 Improved displaying of lists items
added a generator logic for displaying lists, that's following this algorithm: - yield the format - yield the header (as tuple matching the format) - yield content line by line (as tuple matching the format) and do not display the header when not sending to a tty and do not display the "cursor display" character voodoo when not sending to a tty. fixes #114 Signed-off-by: Guyzmo <guyzmo+github+pub@m0g.net>
1 parent 244d189 commit 583734f

File tree

9 files changed

+185
-194
lines changed

9 files changed

+185
-194
lines changed

git_repo/repo.py

Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
from .exceptions import ArgumentError, ResourceNotFoundError
138138
from .services.service import RepositoryService
139139

140+
from .tools import print_tty, print_iter, loop_input, confirm
140141
from .kwargparse import KeywordArgumentParser, store_parameter, register_action
141142

142143
from git import Repo, Git
@@ -146,28 +147,6 @@
146147

147148
EXTRACT_URL_RE = re.compile('[^:]*(://|@)[^/]*/')
148149

149-
def loop_input(*args, method=input, **kwarg):
150-
out = ''
151-
while len(out) == 0:
152-
out = method(*args, **kwarg)
153-
return out
154-
155-
def confirm(what, where):
156-
'''
157-
Method to show a CLI based confirmation message, waiting for a yes/no answer.
158-
"what" and "where" are used to better define the message.
159-
'''
160-
ans = input('Are you sure you want to delete the '
161-
'{} {} from the service?\n[yN]> '.format(what, where))
162-
if 'y' in ans:
163-
ans = loop_input('Are you really sure? there\'s no coming back!\n'
164-
'[type \'burn!\' to proceed]> ')
165-
if 'burn!' != ans:
166-
return False
167-
else:
168-
return False
169-
return True
170-
171150

172151
class GitRepoRunner(KeywordArgumentParser):
173152

@@ -289,8 +268,7 @@ def store_gitconfig(self, val):
289268
@register_action('ls')
290269
@register_action('list')
291270
def do_list(self):
292-
service = self.get_service(False)
293-
service.list(self.user, self.long)
271+
print_iter(self.get_service(False).list(self.user, self.long))
294272
return 0
295273

296274
@register_action('add')
@@ -409,10 +387,8 @@ def do_open(self):
409387
@register_action('request', 'list')
410388
def do_request_list(self):
411389
service = self.get_service(lookup_repository=self.repo_slug == None)
412-
log.info('List of open requests to merge:')
413-
log.info(" {}\t{}\t{}".format('id', 'title'.ljust(60), 'URL'))
414-
for pr in service.request_list(self.user_name, self.repo_name):
415-
print("{}\t{}\t{}".format(pr[0].rjust(3), pr[1][:60].ljust(60), pr[2]))
390+
print_tty('List of open requests to merge:')
391+
print_iter(service.request_list(self.user_name, self.repo_name))
416392
return 0
417393

418394
@register_action('request', 'create')
@@ -494,16 +470,7 @@ def do_request_fetch(self):
494470
@register_action('snippet', 'list')
495471
def do_gist_list(self):
496472
service = self.get_service(lookup_repository=False)
497-
if 'github' == service.name and self.gist_ref:
498-
log.info("{:15}\t{:>7}\t{}".format('language', 'size', 'name'))
499-
else:
500-
log.info("{:56}\t{}".format('id', 'title'.ljust(60)))
501-
if self.gist_ref:
502-
for gist_file in service.gist_list(self.gist_ref):
503-
print("{:15}\t{:7}\t{}".format(*gist_file))
504-
else:
505-
for gist in service.gist_list():
506-
print( "{:56}\t{}".format(gist[0], gist[1]))
473+
print_iter(service.gist_list(self.gist_ref or None))
507474
return 0
508475

509476
@register_action('gist', 'clone')
@@ -638,7 +605,8 @@ def cli(): #pragma: no cover
638605
sys.exit(main(docopt(__doc__.format(self=sys.argv[0].split('/')[-1], version=__version__))))
639606
finally:
640607
# Whatever happens, make sure that the cursor reappears with some ANSI voodoo
641-
sys.stdout.write('\033[?25h')
608+
if sys.stdout.isatty():
609+
sys.stdout.write('\033[?25h')
642610

643611
if __name__ == '__main__': #pragma: no cover
644612
cli()

git_repo/services/ext/github.py

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55

66
from ..service import register_target, RepositoryService, os
77
from ...exceptions import ResourceError, ResourceExistsError, ResourceNotFoundError, ArgumentError
8+
from ...tools import columnize
89

910
import github3
1011

1112
from git.exc import GitCommandError
1213

14+
from datetime import datetime
15+
1316
@register_target('hub', 'github')
1417
class GithubService(RepositoryService):
1518
fqdn = 'github.com'
@@ -75,37 +78,18 @@ def delete(self, repo, user=None):
7578
raise ResourceError('Unhandled exception: {}'.format(err)) from err
7679

7780
def list(self, user, _long=False):
78-
import shutil, sys
79-
from datetime import datetime
80-
term_width = shutil.get_terminal_size((80, 20)).columns
81-
def col_print(lines, indent=0, pad=2):
82-
# prints a list of items in a fashion similar to the dir command
83-
# borrowed from https://gist.github.com/critiqjo/2ca84db26daaeb1715e1
84-
n_lines = len(lines)
85-
if n_lines == 0:
86-
return
87-
col_width = max(len(line) for line in lines)
88-
n_cols = int((term_width + pad - indent)/(col_width + pad))
89-
n_cols = min(n_lines, max(1, n_cols))
90-
col_len = int(n_lines/n_cols) + (0 if n_lines % n_cols == 0 else 1)
91-
if (n_cols - 1) * col_len >= n_lines:
92-
n_cols -= 1
93-
cols = [lines[i*col_len : i*col_len + col_len] for i in range(n_cols)]
94-
rows = list(zip(*cols))
95-
rows_missed = zip(*[col[len(rows):] for col in cols[:-1]])
96-
rows.extend(rows_missed)
97-
for row in rows:
98-
print(" "*indent + (" "*pad).join(line.ljust(col_width) for line in row))
99-
10081
if not self.gh.user(user):
10182
raise ResourceNotFoundError("User {} does not exists.".format(user))
10283

10384
repositories = self.gh.iter_user_repos(user)
10485
if not _long:
105-
repositories = list(repositories)
106-
col_print(["/".join([user, repo.name]) for repo in repositories])
86+
repositories = list(["/".join([user, repo.name]) for repo in repositories])
87+
yield "{}"
88+
yield "Total repositories: {}".format(len(repositories))
89+
yield from columnize(repositories)
10790
else:
108-
print('Status\tCommits\tReqs\tIssues\tForks\tCoders\tWatch\tLikes\tLang\tModif\t\tName', file=sys.stderr)
91+
yield "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t\t{}"
92+
yield ['Status', 'Commits', 'Reqs', 'Issues', 'Forks', 'Coders', 'Watch', 'Likes', 'Lang', 'Modif', 'Name']
10993
for repo in repositories:
11094
try:
11195
if repo.updated_at.year < datetime.now().year:
@@ -119,7 +103,7 @@ def col_print(lines, indent=0, pad=2):
119103
])
120104
nb_pulls = len(list(repo.iter_pulls()))
121105
nb_issues = len(list(repo.iter_issues())) - nb_pulls
122-
print('\t'.join([
106+
yield [
123107
# status
124108
status,
125109
# stats
@@ -134,10 +118,10 @@ def col_print(lines, indent=0, pad=2):
134118
repo.language or '?', # language
135119
repo.updated_at.strftime(date_fmt), # date
136120
'/'.join([user, repo.name]), # name
137-
]))
121+
]
138122
except Exception as err:
139123
if 'Git Repository is empty.' == err.args[0].json()['message']:
140-
print('\t'.join([
124+
yield [
141125
# status
142126
'E',
143127
# stats
@@ -152,7 +136,7 @@ def col_print(lines, indent=0, pad=2):
152136
'?', # language
153137
repo.updated_at.strftime(date_fmt), # date
154138
'/'.join([user, repo.name]), # name
155-
]))
139+
]
156140
else:
157141
print("Cannot show repository {}: {}".format('/'.join([user, repo.name]), err))
158142

@@ -167,12 +151,16 @@ def _format_gist(self, gist):
167151

168152
def gist_list(self, gist=None):
169153
if not gist:
154+
yield "{:45.45} {}"
155+
yield 'title', 'url'
170156
for gist in self.gh.iter_gists(self.gh.user().login):
171-
yield (gist.html_url, gist.description)
157+
yield gist.description, gist.html_url
172158
else:
173159
gist = self.gh.gist(self._format_gist(gist))
174160
if gist is None:
175161
raise ResourceNotFoundError('Gist does not exists.')
162+
yield "{:15}\t{:7}\t{}"
163+
yield 'language', 'size', 'name'
176164
for gist_file in gist.iter_files():
177165
yield (gist_file.language if gist_file.language else 'Raw text',
178166
gist_file.size,
@@ -285,8 +273,10 @@ def request_create(self, user, repo, from_branch, onto_branch, title=None, descr
285273

286274
def request_list(self, user, repo):
287275
repository = self.gh.repository(user, repo)
276+
yield "{}\t{:<60}\t{}"
277+
yield 'id', 'title', 'URL'
288278
for pull in repository.iter_pulls():
289-
yield ( str(pull.number), pull.title, pull.links['html'] )
279+
yield str(pull.number), pull.title, pull.links['html']
290280

291281
def request_fetch(self, user, repo, request, pull=False, force=False):
292282
if pull:

git_repo/services/ext/gitlab.py

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from ..service import register_target, RepositoryService
77
from ...exceptions import ArgumentError, ResourceError, ResourceExistsError, ResourceNotFoundError
8+
from ...tools import columnize
89

910
import gitlab
1011
from gitlab.exceptions import GitlabListError, GitlabCreateError, GitlabGetError
@@ -74,37 +75,17 @@ def delete(self, repo, user=None):
7475
raise ResourceError("Unhandled exception: {}".format(err)) from err
7576

7677
def list(self, user, _long=False):
77-
import shutil, sys
78-
from datetime import datetime
79-
term_width = shutil.get_terminal_size((80, 20)).columns
80-
def col_print(lines, indent=0, pad=2):
81-
# prints a list of items in a fashion similar to the dir command
82-
# borrowed from https://gist.github.com/critiqjo/2ca84db26daaeb1715e1
83-
n_lines = len(lines)
84-
if n_lines == 0:
85-
return
86-
col_width = max(len(line) for line in lines)
87-
n_cols = int((term_width + pad - indent)/(col_width + pad))
88-
n_cols = min(n_lines, max(1, n_cols))
89-
col_len = int(n_lines/n_cols) + (0 if n_lines % n_cols == 0 else 1)
90-
if (n_cols - 1) * col_len >= n_lines:
91-
n_cols -= 1
92-
cols = [lines[i*col_len : i*col_len + col_len] for i in range(n_cols)]
93-
rows = list(zip(*cols))
94-
rows_missed = zip(*[col[len(rows):] for col in cols[:-1]])
95-
rows.extend(rows_missed)
96-
for row in rows:
97-
print(" "*indent + (" "*pad).join(line.ljust(col_width) for line in row))
98-
9978
if not self.gl.users.search(user):
10079
raise ResourceNotFoundError("User {} does not exists.".format(user))
10180

10281
repositories = self.gl.projects.list(author=user)
10382
if not _long:
104-
repositories = list(repositories)
105-
col_print([repo.path_with_namespace for repo in repositories])
83+
repositories = list([repo.path_with_namespace for repo in repositories])
84+
yield "{}"
85+
yield "Total repositories: {}".format(len(repositories))
86+
yield from columnize(repositories)
10687
else:
107-
print('Status\tCommits\tReqs\tIssues\tForks\tCoders\tWatch\tLikes\tLang\tModif\t\tName', file=sys.stderr)
88+
yield ['Status', 'Commits', 'Reqs', 'Issues', 'Forks', 'Coders', 'Watch', 'Likes', 'Lang', 'Modif\t', 'Name']
10889
for repo in repositories:
10990
time.sleep(0.5)
11091
# if repo.last_activity_at.year < datetime.now().year:
@@ -116,7 +97,7 @@ def col_print(lines, indent=0, pad=2):
11697
'F' if False else ' ', # is a fork?
11798
'P' if repo.visibility_level == 0 else ' ', # is private?
11899
])
119-
print('\t'.join([
100+
yield [
120101
# status
121102
status,
122103
# stats
@@ -131,7 +112,7 @@ def col_print(lines, indent=0, pad=2):
131112
'N.A.', # language
132113
repo.last_activity_at, # date
133114
repo.name_with_namespace, # name
134-
]))
115+
]
135116

136117
def get_repository(self, user, repo):
137118
try:
@@ -163,10 +144,12 @@ def _deconstruct_snippet_uri(self, uri):
163144
return (user, project_name, snippet_id)
164145

165146
def gist_list(self, project=None):
147+
yield "{:45.45} {}"
148+
yield 'title', 'url'
166149
if not project:
167150
try:
168151
for snippet in self.gl.snippets.list():
169-
yield (snippet.web_url, snippet.title)
152+
yield snippet.title, snippet.web_url
170153
except GitlabListError as err:
171154
if err.response_code == 404:
172155
raise ResourceNotFoundError('Feature not available, please upgrade your gitlab instance.') from err
@@ -177,7 +160,7 @@ def gist_list(self, project=None):
177160
try:
178161
project = self.gl.projects.get(project)
179162
for snippet in project.snippets.list():
180-
yield (snippet.web_url, 0, snippet.title)
163+
yield (snippet.web_url, snippet.title)
181164
except GitlabGetError as err:
182165
raise ResourceNotFoundError('Could not retrieve project "{}".'.format(project)) from err
183166

@@ -303,6 +286,8 @@ def request_create(self, user, repo, local_branch, remote_branch, title, descrip
303286

304287
def request_list(self, user, repo):
305288
project = self.gl.projects.get('/'.join([user, repo]))
289+
yield "{:>3}\t{:<60}\t{:2}"
290+
yield ('id', 'title', 'URL')
306291
for mr in self.gl.project_mergerequests.list(project_id=project.id):
307292
yield ( str(mr.iid),
308293
mr.title,

git_repo/services/ext/gogs.py

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55

66
from ..service import register_target, RepositoryService, os
77
from ...exceptions import ResourceError, ResourceExistsError, ResourceNotFoundError
8+
from ...tools import columnize
89

910
from gogs_client import GogsApi, GogsRepo, Token, UsernamePassword, ApiFailure
1011
from requests import Session, HTTPError
1112
from urllib.parse import urlparse, urlunparse
13+
from datetime import datetime
1214
import functools
1315

1416
from git import config as git_config
@@ -152,32 +154,17 @@ def list(self, user, _long=False):
152154
import shutil, sys
153155
from datetime import datetime
154156
term_width = shutil.get_terminal_size((80, 20)).columns
155-
def col_print(lines, indent=0, pad=2):
156-
# prints a list of items in a fashion similar to the dir command
157-
# borrowed from https://gist.github.com/critiqjo/2ca84db26daaeb1715e1
158-
n_lines = len(lines)
159-
if n_lines == 0:
160-
return
161-
col_width = max(len(line) for line in lines)
162-
n_cols = int((term_width + pad - indent)/(col_width + pad))
163-
n_cols = min(n_lines, max(1, n_cols))
164-
col_len = int(n_lines/n_cols) + (0 if n_lines % n_cols == 0 else 1)
165-
if (n_cols - 1) * col_len >= n_lines:
166-
n_cols -= 1
167-
cols = [lines[i*col_len : i*col_len + col_len] for i in range(n_cols)]
168-
rows = list(zip(*cols))
169-
rows_missed = zip(*[col[len(rows):] for col in cols[:-1]])
170-
rows.extend(rows_missed)
171-
for row in rows:
172-
print(" "*indent + (" "*pad).join(line.ljust(col_width) for line in row))
173157

174158
repositories = self.gg.repositories(user)
175159
if user != self.username and not repositories and user not in self.orgs:
176160
raise ResourceNotFoundError("Unable to list namespace {} - only authenticated user and orgs available for listing.".format(user))
177161
if not _long:
178-
col_print([repo['full_name'] for repo in repositories])
162+
repositories = list([repo['full_name'] for repo in repositories])
163+
yield "{}"
164+
yield "Total repositories: {}".format(len(repositories))
165+
yield from columnize(repositories)
179166
else:
180-
print('Status\tCommits\tReqs\tIssues\tForks\tCoders\tWatch\tLikes\tLang\tModif\t\t\t\tName', file=sys.stderr)
167+
yield ['Status', 'Commits', 'Reqs', 'Issues', 'Forks', 'Coders', 'Watch', 'Likes', 'Lang', 'Modif\t', 'Name']
181168
for repo in repositories:
182169
status = ''.join([
183170
'F' if repo['fork'] else ' ', # is a fork?
@@ -187,7 +174,7 @@ def col_print(lines, indent=0, pad=2):
187174
issues = self.gg._check_ok(self.gg._get('/repos/{}/issues'.format(repo['full_name']), auth=self.auth)).json()
188175
except Exception:
189176
issues = []
190-
print('\t'.join([
177+
yield [
191178
# status
192179
status,
193180
# stats
@@ -202,7 +189,7 @@ def col_print(lines, indent=0, pad=2):
202189
repo.get('language') or '?', # language
203190
repo['updated_at'], # date
204191
repo['full_name'], # name
205-
]))
192+
]
206193

207194
def get_repository(self, user, repo):
208195
try:

0 commit comments

Comments
 (0)