Add post command
[jan-pona-mute.git] / jan-pona-mute.py
CommitLineData
1882b5bc 1#!/usr/bin/env python3
149bc23e
AS
2# Copyright (C) 2019 Alex Schroeder <alex@gnu.org>
3
4# This program is free software: you can redistribute it and/or modify it under
5# the terms of the GNU Affero General Public License as published by the Free
6# Software Foundation, either version 3 of the License, or (at your option) any
7# later version.
8#
9# This program is distributed in the hope that it will be useful, but WITHOUT
10# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
12# details.
13#
14# You should have received a copy of the GNU Affero General Public License along
15# with this program. If not, see <https://www.gnu.org/licenses/>.
16
17import diaspy
6328099f 18import subprocess
149bc23e 19import argparse
6328099f 20import shutil
149bc23e
AS
21import cmd
22import sys
23import os
24
1882b5bc
AS
25_RC_PATHS = (
26 "~/.config/jan-pona-mute/login",
ebc699fd 27 "~/.jan-pona-mute.d/login"
1882b5bc
AS
28 "~/.jan-pona-mute"
29)
30
ebc699fd
AS
31_NOTE_DIRS = (
32 "~/.config/jan-pona-mute/notes",
33 "~/.jan-pona-mute.d/notes"
34)
35
6328099f 36_PAGERS = (
ebc699fd 37 os.getenv("PAGER"),
6328099f
AS
38 "mdcat",
39 "fold",
40 "cat"
41)
42
ebc699fd
AS
43_EDITORS = (
44 os.getenv("EDITOR"),
45 "vi",
46 "ed"
47)
48
82f0d747
AS
49shortcuts = {
50 "q": "quit",
51 "p": "preview",
52 "c": "comments",
53 "r": "reload",
54 "n": "notifications",
55 "e": "edit",
56 "d": "delete",
57 "g": "notifications reload",
58}
59
149bc23e 60def get_rcfile():
ebc699fd
AS
61 """Init file finder"""
62 for path in _RC_PATHS:
63 path = os.path.expanduser(path)
64 if os.path.exists(path):
65 return path
149bc23e
AS
66 return None
67
ebc699fd
AS
68def get_notes_dir():
69 """Notes directory finder"""
70 dir = None
71 for path in _NOTE_DIRS:
72 path = os.path.expanduser(path)
73 if os.path.isdir(path):
74 dir = path
75 break
76 if dir == None:
77 dir = os.path.expanduser(_NOTE_DIRS[0])
78 if not os.path.isdir(dir):
79 os.makedirs(dir)
80 return dir
81
82def get_binary(list):
83 for cmd in list:
84 if cmd != None:
85 bin = shutil.which(cmd)
86 if bin != None:
87 return bin
88
6328099f 89def get_pager():
ebc699fd
AS
90 """Pager finder"""
91 return get_binary(_PAGERS)
92
93def get_editor():
94 """Editor finder"""
95 return get_binary(_EDITORS)
6328099f 96
149bc23e
AS
97class DiasporaClient(cmd.Cmd):
98
99 prompt = "\x1b[38;5;255m" + "> " + "\x1b[0m"
6328099f 100 intro = "Welcome to Diaspora! Use the intro command for a quick introduction."
149bc23e 101
8bf63019
AS
102 header_format = "\x1b[1;38;5;255m" + "%s" + "\x1b[0m"
103
149bc23e
AS
104 username = None
105 pod = None
106 password = None
6328099f 107 pager = None
ebc699fd 108 editor = None
149bc23e
AS
109
110 connection = None
18e74209 111 notifications = []
1ff8fa2c 112 home = None
bec46251 113 numbers_refer_to = None
18e74209 114 post = None
bb8316e5 115 post_cache = {} # key is self.post.uid, and notification.id
f1bfb7bc 116
714a2f67
AS
117 undo = []
118
149bc23e
AS
119
120 # dict mapping user ids to usernames
121 users = {}
122
123 def get_username(self, guid):
124 if guid in self.users:
125 return self.users[guid]
126 else:
127 user = diaspy.people.User(connection = self.connection, guid = guid)
128 self.users[guid] = user.handle()
129 return self.users[guid]
130
6328099f 131 def do_intro(self, line):
d87d6adf 132 """Start here."""
6328099f 133 print("""
bb8316e5
AS
134Use the 'account' and 'password' commands to set up your connection,
135then use the 'login' command to log in. If everything works as
136intended, use the 'save' command to save these commands to an init
137file.
6328099f 138
bec46251
AS
139Once you've listed things such as notifications or the home stream,
140enter a number to select the corresponding item.
6328099f
AS
141""")
142
149bc23e
AS
143 def do_account(self, account):
144 """Set username and pod using the format username@pod."""
145 try:
146 (self.username, self.pod) = account.split('@')
1882b5bc 147 print("Username and pod set: %s@%s" % (self.username, self.pod))
149bc23e
AS
148 except ValueError:
149 print("The account must contain an @ character, e.g. kensanata@pluspora.com.")
150 print("Use the account comand to set the account.")
151
152 def do_info(self, line):
153 """Get some info about things. By default, it is info about yourself."""
82f0d747 154 print(self.header("Info"))
1882b5bc
AS
155 print("Username: %s" % self.username)
156 print("Password: %s" % ("None" if self.password == None else "set"))
157 print("Pod: %s" % self.pod)
6328099f 158 print("Pager: %s" % self.pager)
ebc699fd 159 print("Editor: %s" % self.editor)
bec46251 160 print("Cache: %s posts" % len(self.post_cache))
149bc23e
AS
161
162 def do_password(self, password):
163 """Set the password."""
164 self.password = (None if self.password == "" else password)
1882b5bc
AS
165 print("Password %s" % ("unset" if self.password == "" else "set"))
166
167 def do_save(self, line):
d87d6adf 168 """Save your login information to the init file."""
1882b5bc 169 if self.username == None or self.pod == None:
ebc699fd 170 print("Use the 'account' command to set username and pod.")
1882b5bc 171 elif self.password == None:
ebc699fd 172 print("Use the 'password' command.")
1882b5bc
AS
173 else:
174 rcfile = get_rcfile()
175 if rcfile == None:
ebc699fd 176 rfile = _RC_PATHS[0]
1882b5bc
AS
177 seen_account = False
178 seen_password = False
179 seen_login = False
180 changed = False
181 file = []
182 with open(rcfile, "r") as fp:
183 for line in fp:
184 words = line.strip().split()
185 if words:
186 if words[0] == "account":
187 seen_account = True
188 account = "%s@%s" % (self.username, self.pod)
189 if len(words) > 1 and words[1] != account:
190 line = "account %s\n" % account
191 changed = True
192 elif words[0] == "password":
193 seen_password = True
194 if len(words) > 1 and words[1] != self.password:
195 line = "password %s\n" % self.password
196 changed = True
197 elif words[0] == "login":
198 if seen_account and seen_password:
199 seen_login = True
200 else:
201 # skip login if no account or no password given
202 line = None
203 changed = True
204 if line != None:
205 file.append(line)
206 if not seen_account:
207 file.append("account %s@%s\n" % (self.username, self.pod))
208 changed = True
209 if not seen_password:
210 file.append("password %s\n" % self.password)
211 changed = True
212 if not seen_login:
213 file.append("login\n")
214 changed = True
215 if changed:
216 if os.path.isfile(rcfile):
217 os.rename(rcfile, rcfile + "~")
218 if not os.path.isdir(os.path.dirname(rcfile)):
219 os.makedirs(os.path.dirname(rcfile))
220 with open(rcfile, "w") as fp:
221 fp.write("".join(file))
222 print("Wrote %s" % rcfile)
223 else:
224 print("No changes made, %s left unchanged" % rcfile)
149bc23e
AS
225
226 def do_login(self, line):
227 """Login."""
228 if line != "":
1882b5bc
AS
229 self.onecmd("account %s" % line)
230 if self.username == None or self.pod == None:
ebc699fd 231 print("Use the 'account' command to set username and pod.")
1882b5bc 232 elif self.password == None:
ebc699fd 233 print("Use the 'password' command.")
1882b5bc
AS
234 else:
235 self.connection = diaspy.connection.Connection(
236 pod = "https://%s" % self.pod, username = self.username, password = self.password)
237 try:
238 self.connection.login()
1882b5bc 239 except diaspy.errors.LoginError:
ebc699fd 240 print("Login failed.")
149bc23e 241
6328099f
AS
242 def do_pager(self, pager):
243 """Set the pager, e.g. to cat"""
244 self.pager = pager
245 print("Pager set: %s" % self.pager)
246
ebc699fd
AS
247 def do_editor(self, editor):
248 """Set the editor, e.g. to ed"""
249 self.editor = editor
250 print("Editor set: %s" % self.editor)
251
8bf63019
AS
252 def header(self, line):
253 """Wrap line in header format."""
254 return self.header_format % line
255
149bc23e 256 def do_notifications(self, line):
bb8316e5
AS
257 """List notifications. Use 'notifications reload' to reload them."""
258 if line == "" and self.notifications:
259 print("Redisplaying the notifications in the cache.")
260 print("Use the 'reload' argument to reload them.")
261 elif line == "reload" or not self.notifications:
262 if self.connection == None:
263 print("Use the 'login' command, first.")
264 return
265 self.notifications = diaspy.notifications.Notifications(self.connection).last()
ebc699fd
AS
266 else:
267 print("The 'notifications' command only takes the optional argument 'reload'.")
268 return
bb8316e5
AS
269 if self.notifications:
270 for n, notification in enumerate(self.notifications):
82f0d747
AS
271 if notification.unread:
272 print(self.header("%2d. %s %s") % (n+1, notification.when(), notification))
273 else:
274 print("%2d. %s %s" % (n+1, notification.when(), notification))
bb8316e5 275 print("Enter a number to select the notification.")
bec46251 276 self.numbers_refer_to = 'notifications'
bb8316e5
AS
277 else:
278 print("There are no notifications. 😢")
149bc23e
AS
279
280 ### The end!
281 def do_quit(self, *args):
282 """Exit jan-pona-mute."""
283 print("Be safe!")
284 sys.exit()
285
286 def default(self, line):
287 if line.strip() == "EOF":
288 return self.onecmd("quit")
289
290 # Expand abbreviated commands
291 first_word = line.split()[0].strip()
82f0d747
AS
292 if first_word in shortcuts:
293 full_cmd = shortcuts[first_word]
149bc23e
AS
294 expanded = line.replace(first_word, full_cmd, 1)
295 return self.onecmd(expanded)
296
bb8316e5
AS
297 # Finally, see if it's a notification and show it
298 self.do_show(line)
1882b5bc 299
bb8316e5 300 def do_show(self, line):
18e74209 301 """Show the post given by the index number.
bec46251 302The index number must refer to the current list of notifications
1ff8fa2c
AS
303or the home stream. If no index number is given, show the current
304post again."""
bec46251
AS
305 if not self.notifications and not self.home:
306 print("Use the 'login' command to load notifications.")
bb8316e5 307 return
1ff8fa2c 308 if line == "" and self.post == None:
bec46251 309 print("Please specify a number.")
bb8316e5 310 return
1ff8fa2c
AS
311 if line != "":
312 try:
313 n = int(line.strip())
314 if self.numbers_refer_to == 'notifications':
315 notification = self.notifications[n-1]
316 self.show(notification)
317 self.load(notification.about())
318 elif self.numbers_refer_to == 'home':
319 self.post = self.home[n-1]
320 else:
321 print("Internal error: not sure what numbers '%s' refer to." % self.numbers_refer_to)
322 return
323 except ValueError:
324 print("The 'show' command takes a notification number but '%s' is not a number" % line)
325 return
326 except IndexError:
327 print("Index too high!")
bec46251 328 return
1882b5bc 329
18e74209
AS
330 print()
331 self.show(self.post)
1882b5bc 332
bb8316e5
AS
333 if(self.post.comments):
334 print()
88c1a828
AS
335 if len(self.post.comments) == 1:
336 print("There is 1 comment.")
337 else:
338 print("There are %d comments." % len(self.post.comments))
bb8316e5
AS
339 print("Use the 'comments' command to list the latest comments.")
340
341 def load(self, id):
342 """Load the post belonging to the id (from a notification),
343or get it from the cache."""
344 if id in self.post_cache:
345 self.post = self.post_cache[id]
ebc699fd 346 print("Retrieved post from the cache.")
bb8316e5
AS
347 else:
348 print("Loading...")
349 self.post = diaspy.models.Post(connection = self.connection, id = id)
350 self.post_cache[id] = self.post
351 return self.post
352
353 def do_reload(self, line):
354 """Reload the current post."""
355 if self.post == None:
356 print("Use the 'show' command to show a post, first.")
357 return
358 print("Reloading...")
359 self.post = diaspy.models.Post(connection = self.connection, id = self.post.id)
360 self.post_cache[id] = self.post
361
1882b5bc
AS
362 def show(self, item):
363 """Show the current item."""
6328099f 364 if self.pager:
88c1a828 365 subprocess.run(self.pager, input = str(item), text = True)
6328099f 366 else:
88c1a828 367 print(str(item))
6328099f 368
18e74209 369 def do_comments(self, line):
bb8316e5
AS
370 """Show the comments for the current post.
371Use the 'all' argument to show them all. Use a numerical argument to
372show that many. The default is to load the last five."""
18e74209 373 if self.post == None:
bb8316e5 374 print("Use the 'show' command to show a post, first.")
18e74209
AS
375 return
376 if self.post.comments == None:
377 print("The current post has no comments.")
378 return
379
380 n = 5
381 comments = self.post.comments
382
383 if line == "all":
384 n = None
385 elif line != "":
6328099f
AS
386 try:
387 n = int(line.strip())
388 except ValueError:
ebc699fd
AS
389 print("The 'comments' command takes a number as its argument, or 'all'.")
390 print("The default is to show the last 5 comments.")
6328099f 391 return
6328099f 392
ebc699fd
AS
393 if n == None:
394 start = 0
395 else:
396 # n is from the back
397 start = max(len(comments) - n, 0)
18e74209 398
42716c49 399 if comments:
ebc699fd 400 for n, comment in enumerate(comments[start:], start):
42716c49
AS
401 print()
402 print(self.header("%2d. %s %s") % (n+1, comment.when(), comment.author()))
403 print()
404 self.show(comment)
405 else:
406 print("There are no comments on the selected post.")
1882b5bc 407
714a2f67 408 def do_comment(self, line):
ebc699fd
AS
409 """Leave a comment on the current post.
410If you just use a number as your comment, it will refer to a note.
411Use the 'edit' command to edit notes."""
714a2f67 412 if self.post == None:
bb8316e5 413 print("Use the 'show' command to show a post, first.")
714a2f67 414 return
ebc699fd 415 try:
9200e1e1 416 # if the comment is just a number, use a note to post
ebc699fd
AS
417 n = int(line.strip())
418 notes = self.get_notes()
419 if notes:
420 try:
7e2b259d 421 line = self.read_note(notes[n-1])
ebc699fd
AS
422 print("Using note #%d: %s" % (n, notes[n-1]))
423 except IndexError:
424 print("Use the 'list notes' command to list valid numbers.")
425 return
426 else:
427 print("There are no notes to use.")
428 return
429 except ValueError:
9200e1e1
AS
430 # in which case we'll simply comment with the line
431 pass
714a2f67
AS
432 comment = self.post.comment(line)
433 self.post.comments.add(comment)
ebc699fd 434 self.undo.append("delete comment %s from %s" % (comment.id, self.post.id))
714a2f67
AS
435 print("Comment posted.")
436
1ff8fa2c
AS
437 def do_post(self, line):
438 """Write a post on the current stream.
439If you just use a number as your post, it will refer to a note.
440Use the 'edit' command to edit notes."""
441 if self.home == None:
442 self.home = diaspy.streams.Stream(self.connection)
443 try:
444 # if the post is just a number, use a note to post
445 n = int(line.strip())
446 notes = self.get_notes()
447 if notes:
448 try:
449 line = self.read_note(notes[n-1])
450 print("Using note #%d: %s" % (n, notes[n-1]))
451 except IndexError:
452 print("Use the 'list notes' command to list valid numbers.")
453 return
454 else:
455 print("There are no notes to use.")
456 return
457 except ValueError:
458 # in which case we'll simply post the line
459 pass
460 self.post = self.home.post(text = line)
461 self.post_cache[self.post.id] = self.post
462 self.undo.append("delete post %s" % self.post.id)
463 print("Posted. Use the 'show' command to show it.")
464
f1bfb7bc
AS
465 def do_delete(self, line):
466 """Delete a comment."""
467 words = line.strip().split()
468 if words:
469 if words[0] == "comment":
ebc699fd 470 if len(words) == 4:
b5d7f34e
AS
471 post = self.post_cache[words[3]]
472 post.delete_comment(words[1])
473 comments = [c.id for c in post.comments if c.id != id]
474 post.comments = diaspy.models.Comments(comments)
ebc699fd
AS
475 print("Comment deleted.")
476 return
b5d7f34e
AS
477 if self.post == None:
478 print("Use the 'show' command to show a post, first.")
479 return
ebc699fd
AS
480 if len(words) == 2:
481 try:
482 n = int(words[1])
483 comment = self.post.comments[n-1]
484 id = comment.id
485 except ValueError:
486 print("Deleting a comment requires an integer.")
487 return
488 except IndexError:
489 print("Use the 'comments' command to find valid comment numbers.")
490 return
491 # not catching any errors from diaspy
492 self.post.delete_comment(id)
493 # there is no self.post.comments.remove(id)
494 comments = [c.id for c in self.post.comments if c.id != id]
495 self.post.comments = diaspy.models.Comments(comments)
496 print("Comment deleted.")
497 return
498 else:
499 print("Deleting a comment requires a comment id and a post id, or a number.")
bb8316e5 500 print("delete comment <comment id> from <post id>")
ebc699fd
AS
501 print("delete comment 5")
502 return
503 if words[0] == "note":
504 if len(words) != 2:
505 print("Deleting a note requires a number.")
506 return
507 try:
508 n = int(words[1])
509 except ValueError:
510 print("Deleting a note requires an integer.")
f1bfb7bc 511 return
ebc699fd
AS
512 notes = self.get_notes()
513 if notes:
514 try:
515 os.unlink(self.get_note_path(notes[n-1]))
516 print("Deleted note #%d: %s" % (n, notes[n-1]))
517 except IndexError:
518 print("Use the 'list notes' command to list valid numbers.")
519 else:
520 print("There are no notes to delete.")
f1bfb7bc 521 else:
ebc699fd 522 print("Things to delete: comment, note.")
f1bfb7bc
AS
523 return
524 else:
525 print("Delete what?")
526
527 def do_undo(self, line):
528 """Undo an action."""
529 if line != "":
ebc699fd 530 print("The 'undo' command does not take an argument.")
f1bfb7bc
AS
531 return
532 if not self.undo:
533 print("There is nothing to undo.")
534 return
535 return self.onecmd(self.undo.pop())
536
ebc699fd
AS
537 def do_edit(self, line):
538 """Edit a note with a given name."""
539 if line == "":
540 print("Edit takes the name of a note as an argument.")
541 return
542 file = self.get_note_path(line)
543 if self.editor:
544 subprocess.run([self.editor, file])
cc59946e 545 self.onecmd("notes")
ebc699fd
AS
546 else:
547 print("Use the 'editor' command to set an editor.")
548
549 def do_notes(self, line):
550 """List notes"""
551 if line != "":
552 print("The 'notes' command does not take an argument.")
553 return
554 notes = self.get_notes()
555 if notes:
556 for n, note in enumerate(notes):
557 print(self.header("%2d. %s") % (n+1, note))
1ff8fa2c
AS
558 print("Use the 'edit' command to edit a note.")
559 print("Use the 'preview' command to look at a note.")
560 print("Use the 'post' command to post a note.")
561 print("Use the 'comment' command to post a comment.")
ebc699fd 562 else:
1ff8fa2c 563 print("Use 'edit' to create a note.")
ebc699fd
AS
564
565 def get_notes(self):
566 """Get the list of notes."""
567 return os.listdir(get_notes_dir())
568
569 def get_note_path(self, filename):
570 """Get the correct path for a note."""
571 return os.path.join(get_notes_dir(), filename)
572
7e2b259d
AS
573 def read_note(self, filename):
574 """Get text of a note."""
575 with open(self.get_note_path(filename), mode = 'r', encoding = 'utf-8') as fp:
576 return fp.read()
577
f38b656c
AS
578 def do_preview(self, line):
579 """Preview a note using your pager.
580Use the 'pager' command to set your pager to something like 'mdcat'."""
581 if line == "":
582 print("The 'preview' command the number of a note as an argument.")
583 print("Use the 'notes' command to list all your notes.")
584 return
585 try:
586 n = int(line.strip())
587 notes = self.get_notes()
588 if notes:
589 try:
590 self.show(self.read_note(notes[n-1]))
591 except IndexError:
592 print("Use the 'list notes' command to list valid numbers.")
593 return
594 else:
595 print("There are no notes to preview.")
596 return
597 except ValueError:
598 print("The 'preview' command takes a number as its argument.")
599 return
600
bec46251
AS
601 def do_home(self, line):
602 """Show the main stream containing the combined posts of the
603followed users and tags and the community spotlights posts if
604the user enabled those."""
605 if line == "":
606 if self.home:
607 print("Redisplaying the cached statuses of the home stream.")
608 print("Use the 'reload' argument to reload them.")
609 print("Use the 'all' argument to show them all.")
610 print("Use a number to show only that many.")
611 print("The default is 5.")
612 else:
613 print("Loading...")
614 self.home = diaspy.streams.Stream(self.connection)
615 self.home.fill()
616 for post in self.home:
617 if post.id not in self.post_cache:
618 self.post_cache[post.id] = post
619 elif line == "reload":
620 if self.connection == None:
621 print("Use the 'login' command, first.")
622 return
623 if self.home:
624 print("Reloading...")
625 self.home.update()
626 line = ""
627 else:
628 self.home = diaspy.streams.Stream(self.connection)
629 self.home.fill()
630
631 n = 5
1ff8fa2c 632 posts = sorted(self.home, key=lambda x: x.data()["created_at"])
bec46251
AS
633
634 if line == "all":
635 n = None
636 elif line != "":
637 try:
638 n = int(line.strip())
639 except ValueError:
640 print("The 'home' command takes a number as its argument, or 'reload' or 'all'.")
641 print("The default is to show the last 5 posts.")
642 return
643
644 if n == None:
645 start = 0
646 else:
647 # n is from the back
648 start = max(len(posts) - n, 0)
649
650 if posts:
651 for n, post in enumerate(posts[start:], start):
652 print()
653 print(self.header("%2d. %s %s") % (n+1, post.data()["created_at"], post.author()))
654 print()
655 self.show(post)
656
657 print("Enter a number to select the post.")
658 self.numbers_refer_to = 'home'
659 else:
660 print("The people you follow have nothing to say.")
661 print("The tags you follow are empty. 😢")
662
82f0d747
AS
663 def do_shortcuts(self, line):
664 """List all shortcuts."""
665 if line != "":
666 print("The 'shortcuts' command does not take an argument.")
667 return
668 print(self.header("Shortcuts"))
669 for shortcut in sorted(shortcuts):
670 print("%s\t%s" % (shortcut, shortcuts[shortcut]))
671 print("Use the 'shortcut' command to change or add shortcuts.")
672
673 def do_shortcut(self, line):
674 """Define a new shortcut."""
675 words = line.strip().split(maxsplit = 1)
676 if len(words) == 0:
677 return self.onecmd("shortcuts")
678 elif len(words) == 1:
679 shortcut = words[0]
680 if shortcut in shortcuts:
681 print("%s\t%s" % (shortcut, shortcuts[shortcut]))
682 else:
683 print("%s is not a shortcut" % shortcut)
684 else:
685 shortcuts[words[0]] = words[1]
686
149bc23e
AS
687# Main function
688def main():
689
690 # Parse args
691 parser = argparse.ArgumentParser(description='A command line Diaspora client.')
1882b5bc
AS
692 parser.add_argument('--no-init-file', dest='init_file', action='store_const',
693 const=False, default=True, help='Do not load a init file')
149bc23e
AS
694 args = parser.parse_args()
695
696 # Instantiate client
1882b5bc
AS
697 c = DiasporaClient()
698
699 # Process init file
6328099f 700 seen_pager = False
ebc699fd 701 seen_editor = False
1882b5bc
AS
702 if args.init_file:
703 rcfile = get_rcfile()
704 if rcfile:
705 print("Using init file %s" % rcfile)
706 with open(rcfile, "r") as fp:
707 for line in fp:
708 line = line.strip()
6328099f
AS
709 if line != "":
710 c.cmdqueue.append(line)
711 if not seen_pager:
712 seen_pager = line.startswith("pager ");
ebc699fd
AS
713 if not seen_editor:
714 seen_editor = line.startswith("editor ");
1882b5bc 715 else:
ebc699fd 716 print("Use the 'save' command to save your login sequence to an init file.")
149bc23e 717
6328099f
AS
718 if not seen_pager:
719 # prepend
720 c.cmdqueue.insert(0, "pager %s" % get_pager())
ebc699fd
AS
721 if not seen_editor:
722 # prepend
723 c.cmdqueue.insert(0, "editor %s" % get_editor())
6328099f 724
149bc23e
AS
725 # Endless interpret loop
726 while True:
727 try:
728 c.cmdloop()
729 except KeyboardInterrupt:
730 print("")
731
732if __name__ == '__main__':
733 main()