├── setup.cfg ├── .travis.yml ├── examples ├── test_all.py ├── banner.py ├── progress.py ├── loading.py └── installer.py ├── setup.py ├── draftlog ├── __init__.py ├── ansi.py ├── lcs.py ├── logdraft.py ├── drafter.py └── loading.py ├── LICENSE ├── .gitignore └── README.md /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.5" 5 | 6 | script: 7 | - python setup.py install 8 | - python examples/banner.py 9 | - python examples/installer.py 10 | - python examples/loading.py 11 | - python examples/progress.py -------------------------------------------------------------------------------- /examples/test_all.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | print ("Banner.py:") 4 | os.system("python examples/banner.py") 5 | 6 | print ("\nInstaller.py:") 7 | os.system("python examples/installer.py") 8 | 9 | print ("\nLoading.py:") 10 | os.system("python examples/loading.py") 11 | 12 | print ("\nProgress.py:") 13 | os.system("python examples/progress.py") -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name = 'draftlog', 5 | version = '2.0.9', 6 | description = 'Create updatable log lines into the terminal.', 7 | url = 'https://github.com/kepoorhampond/python-draftlog', 8 | author = 'Kepoor Hampond', 9 | author_email = 'kepoorh@gmail.com', 10 | license = 'MIT', 11 | packages = ['draftlog'], 12 | install_requires= [ 13 | 'colorama' # This package will ensure ANSI support for windows cmd 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /draftlog/__init__.py: -------------------------------------------------------------------------------- 1 | from draftlog.lcs import LineCountStream 2 | from draftlog.drafter import Drafter 3 | import sys 4 | 5 | import colorama 6 | 7 | """ 8 | To inject the Drafter into sys.stdout which is 9 | routed through with "print". It returns the Drafter 10 | object. 11 | """ 12 | def inject(): # TODO: Make a thread version and a frame version. 13 | colorama.init() 14 | sys.stdout = LineCountStream() 15 | return Drafter() 16 | 17 | # The exception to raise to exit your objects loop. 18 | class Exception(Exception): 19 | pass 20 | -------------------------------------------------------------------------------- /examples/banner.py: -------------------------------------------------------------------------------- 1 | import draftlog 2 | 3 | draft = draftlog.inject() 4 | 5 | class Banner: 6 | def __init__(self, string): 7 | self.string = string 8 | self.counter = 0 9 | def scroll(self): 10 | if self.counter >= 60: 11 | raise draftlog.Exception 12 | self.counter += 1 13 | self.string = self.string[1:] + self.string[0] 14 | return draftlog.ansi.YELLOW + self.string + draftlog.ansi.END 15 | 16 | string = " Wow! Banners! This is so cool! All with draftlog! " 17 | 18 | print ("\n") 19 | print ("*" * len(string)) 20 | banner = draft.log() 21 | print ("*" * len(string)) 22 | print ("\n") 23 | 24 | banner.set_interval(Banner(string).scroll, 0.1) 25 | 26 | draft.start() -------------------------------------------------------------------------------- /examples/progress.py: -------------------------------------------------------------------------------- 1 | import draftlog 2 | from draftlog.ansi import * # For colors 3 | from random import randrange 4 | 5 | draft = draftlog.inject() 6 | 7 | def progress_bar(progress): 8 | if progress >= 100: 9 | progress = 100 10 | units = int(progress / 2) 11 | return ("[{0}{1}] " + GREEN + "{2}%" + END).format(BBLUE + "#" * units + END, "-" * (50 - units), progress) 12 | units = int(progress / 2) 13 | return ("[{0}{1}] " + YELLOW + "{2}%" + END).format(BBLUE + "#" * units + END, "-" * (50 - units), progress) 14 | 15 | class Download: 16 | def __init__(self): 17 | self.progress = 0 18 | self.status = True 19 | def interval(self): 20 | if self.progress >= 100: 21 | raise draftlog.Exception 22 | self.progress += randrange(1, 10) 23 | return progress_bar(self.progress) 24 | 25 | 26 | for n in range(1, 10): 27 | log = draft.log() 28 | log.set_interval(Download().interval, 0.1) 29 | 30 | draft.start() 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kepoor Hampond 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /draftlog/ansi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ANSI, as in the escape characters that trigger actions in the terminal. 3 | 4 | Info: 5 | - https://en.wikipedia.org/wiki/ANSI_escape_code 6 | - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html 7 | - http://ascii-table.com/ansi-escape-sequences-vt-100.php 8 | """ 9 | 10 | # The escape character in Python 11 | esc = '\x1b[' 12 | 13 | # The escape characters to clear a line 14 | clearline = esc + '2K' + esc + '1G' 15 | 16 | # To save the cursor state 17 | save = esc + "7" 18 | 19 | # To restore the cursor state 20 | restore = esc + "8" 21 | 22 | # Move up "n" spaces 23 | def up(n=1): 24 | return esc + str(n) + 'A' 25 | 26 | # Move down "n" spaces 27 | def down(n=1): 28 | return esc + str(n) + 'B' 29 | 30 | def code(code1): 31 | return "\x1b[%sm" % str(code1) 32 | 33 | END = code(0) 34 | BOLD = code(1) 35 | DIM = code(2) 36 | RED = code(31) 37 | GREEN = code(32) 38 | YELLOW = code(33) 39 | BLUE = code(34) 40 | PURPLE = code(35) 41 | CYAN = code(36) 42 | GRAY = code(37) 43 | BRED = RED + BOLD 44 | BGREEN = GREEN + BOLD 45 | BYELLOW = YELLOW + BOLD 46 | BBLUE = BLUE + BOLD 47 | BPURPLE = PURPLE + BOLD 48 | BCYAN = CYAN + BOLD 49 | BGRAY = GRAY + BOLD -------------------------------------------------------------------------------- /draftlog/lcs.py: -------------------------------------------------------------------------------- 1 | """ 2 | LCS: Line Count Stream 3 | """ 4 | from draftlog.logdraft import LogDraft 5 | from draftlog.drafter import Drafter 6 | import os 7 | import subprocess 8 | import sys 9 | 10 | """ 11 | An object inserted into "sys.stdout" in order to 12 | keep track of how many lines have been logged. 13 | """ 14 | class LineCountStream(object): 15 | def __init__(self): 16 | self.data = "" 17 | self.stdout = sys.stdout 18 | self.lines = 1 19 | self.logs = 0 20 | self.editing = False 21 | 22 | # Reads the command "tput lines" if valid 23 | try: 24 | self.rows = subprocess.Popen("tput lines").read() 25 | except (ValueError, IOError, OSError): 26 | self.rows = 20 27 | 28 | """ 29 | The function that overwrites "sys.stdout.write", and 30 | counts the number of lines in what is being "printed". 31 | """ 32 | def write(self, data): 33 | if not self.editing: 34 | self.count_lines(data) 35 | self.stdout.write(data) 36 | 37 | def flush(self): 38 | self.stdout.flush() 39 | 40 | # Counts lines 41 | def count_lines(self, data): 42 | datalines = len(str(data).split("\n")) - 1 43 | self.lines += datalines 44 | self.data += data 45 | -------------------------------------------------------------------------------- /examples/loading.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | import draftlog 3 | import time 4 | from draftlog.ansi import * # For colors 5 | 6 | draft = draftlog.inject() 7 | 8 | class Loader: 9 | def __init__(self, text): 10 | self.frames = "⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏".split(" ") 11 | self.frame = -1 12 | self.text = text 13 | def interval(self): 14 | self.frame += 1 15 | if self.frame > len(self.frames) - 2: 16 | self.frame = -1 17 | return (CYAN + "{0} " + BYELLOW + self.text + END + CYAN + " {0}" + END).format(self.frames[self.frame]) 18 | 19 | class Stepper: 20 | def __init__(self, steps): 21 | self.steps = steps 22 | self.step = -1 23 | self.status = True 24 | def interval(self): 25 | self.step += 1 26 | if self.step >= len(self.steps) - 1: 27 | raise draftlog.Exception 28 | return (" > " + CYAN + self.steps[self.step] + END) 29 | 30 | 31 | steps = ['Doing that', 'Then that', 'And after that', 'We will finish', 'In', '3', '2', '1', '0'] 32 | stepper = Stepper(steps) 33 | loader = Loader("Generic Loading") 34 | 35 | draft.log().set_interval(loader.interval, 0.05) 36 | draft.start() 37 | 38 | while True: 39 | try: 40 | time.sleep(1) 41 | print (stepper.interval()) 42 | except draftlog.Exception: 43 | break 44 | except KeyboardInterrupt: 45 | pass 46 | 47 | draft.stop() -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore reference directories 2 | test/ 3 | reference/ 4 | 5 | #================================== 6 | # Github Compiled Python .gitignore 7 | #================================== 8 | 9 | # Byte-compiled / optimized / DLL files 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # C extensions 15 | *.so 16 | 17 | # Distribution / packaging 18 | .Python 19 | env/ 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *,cover 54 | .hypothesis/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # IPython Notebook 78 | .ipynb_checkpoints 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # dotenv 87 | .env 88 | 89 | # virtualenv 90 | venv/ 91 | ENV/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | 96 | # Rope project settings 97 | .ropeproject 98 | -------------------------------------------------------------------------------- /examples/installer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | import draftlog 3 | from random import randrange 4 | from draftlog.ansi import * # For colors 5 | 6 | draft = draftlog.inject() 7 | 8 | def install_progress(package, step, finished=False): 9 | spaces = " " * (15 - len(package)) 10 | if finished: 11 | return DIM + BBLUE + " > " + BYELLOW + package + spaces + BGREEN + "Installed" + END 12 | else: 13 | return BBLUE + " > " + BYELLOW + package + spaces + BLUE + step + END 14 | 15 | class Loader: 16 | def __init__(self, text, status=True): 17 | self.frames = "⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏".split(" ") 18 | self.frame = -1 19 | self.status = status 20 | self.text = text 21 | def interval(self): 22 | if self.frame > len(self.frames) - 2: 23 | self.frame = -1 24 | self.frame += 1 25 | return (BCYAN + "{0} " + YELLOW + self.text + BCYAN + " {0}" + END).format(self.frames[self.frame]) 26 | 27 | class MockInstall: 28 | def __init__(self, package, wait=0): 29 | self.package = package 30 | self.steps = "gathering dependencies downloading dependencies compiling code cleaning up".split(" ") 31 | self.step = 0 32 | self.count = 0 33 | self.wait = wait 34 | self.status = True 35 | def interval(self): 36 | self.count += 1 37 | if self.status == False: 38 | raise draftlog.Exception 39 | if self.count >= self.wait: 40 | if self.step > len(self.steps) - 2: 41 | self.status = False 42 | return install_progress(self.package, self.steps[self.step], finished=True) 43 | self.step += 1 44 | return install_progress(self.package, self.steps[self.step]) 45 | else: 46 | return " " 47 | 48 | 49 | packages = ["irs", "bobloblaw", "youtube-dl", "truffleHog", "numpy", "scipy"] 50 | load = Loader("Installing Packages") 51 | 52 | load_draft = draft.log() 53 | 54 | load_draft.set_interval(load.interval, 0.05, loader=True) 55 | 56 | for i, package in enumerate(packages): 57 | pack = draft.log() 58 | pack.set_interval( 59 | MockInstall(package, wait=i).interval, 60 | round(float(randrange(25, 150) / 100.0), 1) 61 | ) 62 | 63 | draft.start() -------------------------------------------------------------------------------- /draftlog/logdraft.py: -------------------------------------------------------------------------------- 1 | from draftlog import ansi 2 | import sys 3 | 4 | """ 5 | A single line object that saves its relative position 6 | in the terminal. It's responsible for updating itself. 7 | """ 8 | class LogDraft: 9 | def __init__(self, drafter, text="\n"): 10 | self.stream = sys.stdout 11 | self.drafter = drafter 12 | self.valid = True 13 | self.text = text 14 | self.save_line() 15 | self.stream.write(self.text) 16 | 17 | # For if someone wants to call "LogDraft()('update text')" 18 | def __call__(self, text): 19 | self.update(text) 20 | 21 | # Updates the line that "LogDraft" was created on 22 | def update(self, text): 23 | lines_up = self.lines_up() 24 | 25 | if self.off_screen(): # Check if offscreen, if so, don't update the line. 26 | self.valid = False 27 | return 28 | 29 | # Move cursor up 30 | self.stream.write(ansi.up(lines_up)) 31 | 32 | # Clear the line 33 | self.stream.write(ansi.clearline) 34 | 35 | # Write the line 36 | self.write(text) 37 | 38 | # Flush the data 39 | self.stream.flush() 40 | 41 | # Restore cursor position 42 | self.stream.write(ansi.down(lines_up)) 43 | 44 | # Move cursor to the beginning of the line 45 | self.stream.write("\r") 46 | 47 | # Save the current text 48 | self.text = text 49 | 50 | 51 | # What the user gets when they call "draft.log().set_interval(**args**)" 52 | def set_interval(self, func, sec, **args): 53 | self.drafter.add_interval(self, func, sec, **args) 54 | 55 | # Writes to the LCS 56 | def write(self, text): 57 | if self.valid: 58 | self.stream.write(text) 59 | 60 | # Checks if the line being monitored is off screen. 61 | def off_screen(self): 62 | return int(self.lines_up()) >= int(self.stream.rows) 63 | 64 | # Counts how many lines up until the correct line. 65 | def lines_up(self): 66 | return self.stream.lines - self.line 67 | 68 | # Sets the line to be monitored. 69 | def save_line(self, relative=0): 70 | self.line = self.stream.lines + relative 71 | 72 | def current_text(self): 73 | return self.text.strip("\n") 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Draftlog 2 | > :scroll: Fancy logs with expandable tools. Bring life to your terminal! 3 | 4 | [![demo](http://i.imgur.com/rWE21Ts.gif)](http://i.imgur.com/rWE21Ts.gif) 5 | 6 | [![License: MIT](https://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://mit-license.org/) 7 | [![Build Status](https://img.shields.io/travis/kepoorhampond/python-draftlog/master.svg?style=flat-square)](https://travis-ci.org/kepoorhampond/python-draftlog) 8 | [![PyPI](https://img.shields.io/badge/pypi-draftlog-blue.svg?style=flat-square)](https://pypi.python.org/pypi/draftlog) 9 | [![Say Thanks](https://img.shields.io/badge/say-thanks-ff69b4.svg?style=flat-square)](https://saythanks.io/to/kepoorhampond) 10 | 11 | A module useful for CLI's, logs and pretty much any cool multi-line python tool. 12 | 13 | All inspiration goes to [Ivan Seidel](https://github.com/ivanseidel) with [`node-draftlog`](https://github.com/ivanseidel/node-draftlog). 14 | 15 | Works with Python 2 and 3. 16 | 17 | ## Install 18 | ``` 19 | $ pip install draftlog 20 | ``` 21 | 22 | ## Intro Example 23 | Here's about the simplest program with `draftlog` that actually does something: 24 | ```python 25 | import draftlog 26 | import time 27 | 28 | draft = draftlog.inject() 29 | 30 | print ("The line below me will be updated!") 31 | update_me = draft.log("I will be updated!") 32 | print ("The line above me will be updated!") 33 | 34 | time.sleep(3) 35 | 36 | update_me.update("I've been updated!") 37 | ``` 38 | 39 | Here's a more complicated program: a scrolling banner . If you want to see some more examples in this thread, check out the [`examples`](https://github.com/kepoorhampond/python-draftlog/tree/master/examples) folder! 40 | ```python 41 | import draftlog 42 | 43 | draft = draftlog.inject() 44 | 45 | class Banner: 46 | def __init__(self, string): 47 | self.string = string 48 | self.counter = 0 49 | def scroll(self): 50 | if self.counter >= 50: 51 | # This is what exits out of the loop in: 52 | # "draft.log().set_interval" 53 | raise draftlog.Exception 54 | self.counter += 1 55 | self.string = self.string[1:] + self.string[0] 56 | return self.string 57 | 58 | string = " Wow! Banners! This is so cool! All with draftlog! " 59 | 60 | print ("*" * len(string)) 61 | banner = draft.log() 62 | print ("*" * len(string)) 63 | 64 | banner.set_interval(Banner(string).scroll, 0.1) 65 | 66 | draft.start() 67 | 68 | # You can still print stuff after starting the draft as well: 69 | import time 70 | time.sleep(2) 71 | print ("Wow, some more text!") 72 | ``` 73 | 74 | `set_interval` is a function that takes another function and the time to wait. It overwrites the `draft.log()` line with whatever the function returns. The function will stop being called once it `raises draftlog.Exception`. `draft.start()` will actually start all intervals that have been set. 75 | 76 | ### How 77 | `draft.log()` creates a `DraftLog` object that keeps track of what line it was created on. You can call `update(text)` on it to update the line that it's set on. 78 | 79 | `draft.log().set_interval(function, time)` primes an interval in a background threading process called `DaemonDrafter`. When `draft.start()` is called, it generates interval timing based off of the time specified and then runs it in "frames." 80 | 81 | Since I've made the program open-ended, you can create a lot (see the [`examples`](https://github.com/kepoorhampond/python-draftlog/tree/master/examples) folder) of stuff. 82 | 83 | ## Questions 84 | If you still have questions or need some help, check out the [wiki](https://github.com/kepoorhampond/python-draftlog/wiki/) or email me at `kepoorh@gmail.com`, all feedback is appreciated! -------------------------------------------------------------------------------- /draftlog/drafter.py: -------------------------------------------------------------------------------- 1 | from draftlog.logdraft import LogDraft 2 | from draftlog import ansi 3 | import draftlog 4 | import time 5 | import sys 6 | import threading 7 | 8 | # Imports the correct module according to 9 | # Python version. 10 | if sys.version_info[0] <= 2: 11 | import Queue as queue 12 | else: 13 | import queue 14 | 15 | """ 16 | A background process to coordinate all the intervals 17 | with their correct times rather than having clashing 18 | multiple threads. 19 | """ 20 | class DaemonDrafter(threading.Thread): 21 | def __init__(self): 22 | super(DaemonDrafter, self).__init__() 23 | 24 | self.lcs = sys.stdout 25 | self.intervals = [] 26 | self.counter = -1 27 | self.time_interval = 0 28 | self.end = False 29 | 30 | """ 31 | What actually adds the interval. 32 | "Loader" specifies if the interval should 33 | affect when the draft actually exits. 34 | line, or to add a new line afterwards. 35 | """ 36 | def add_interval(self, logdraft, func, seconds, loader=False): 37 | if loader != None: 38 | loader = not loader 39 | 40 | self.intervals.append({ 41 | "function": func, 42 | "logdraft": logdraft, 43 | "time" : seconds, 44 | "backup" : "", 45 | "status" : loader, 46 | }) 47 | self.sort_intervals() 48 | 49 | # Generates correct timing for intervals 50 | def sort_intervals(self): 51 | smallest = lambda x: x["time"] 52 | sort = sorted(self.intervals, key=smallest) 53 | self.smallest_interval = min(sort, key=smallest) 54 | self.time_interval = self.smallest_interval["time"] 55 | for interval in self.intervals: 56 | interval["increment_on"] = int(round(interval["time"] / self.time_interval)) 57 | interval["backup"] = "" # This is an important thing to change/remember 58 | 59 | # Parses interval output according to its statuses 60 | def parse_interval_output(self, interval): 61 | try: 62 | if self.counter % interval["increment_on"] == 0: 63 | output = interval["function"]() 64 | interval["backup"] = output 65 | else: 66 | output = interval["backup"] 67 | except draftlog.Exception: 68 | output = interval["backup"] 69 | interval["status"] = False 70 | 71 | return str(output) 72 | 73 | # What actually updates the LogDraft lines. 74 | def run_intervals(self): 75 | for interval in self.intervals: 76 | text = self.parse_interval_output(interval) 77 | interval["logdraft"].update(text) 78 | 79 | # Checks if all intervals are done. 80 | def check_done(self): 81 | return all(x["status"] in (False, None) for x in self.intervals) 82 | 83 | # The actual running loop for updating intervals. 84 | def run(self): 85 | lines = 0 86 | while self.check_done() == False and self.end == False: 87 | self.counter += 1 88 | self.run_intervals() 89 | time.sleep(self.time_interval) 90 | self.lcs.write(ansi.clearline) 91 | sys.exit() 92 | 93 | def stop(self): 94 | self.end = True 95 | 96 | """ 97 | Pretty much just a wrapper for "DaemonDrafter". 98 | It's what the user actually interacts with and what 99 | "draftlog.inject()" returns. 100 | """ 101 | class Drafter: 102 | def __init__(self): 103 | self.daemon_drafter = DaemonDrafter() 104 | self.lcs = self.daemon_drafter.lcs 105 | 106 | # Returns a "LogDraft" object on the correct line 107 | def log(self, text="\n"): 108 | if text != "\n": text = text + "\n" 109 | logdraft = LogDraft(self.daemon_drafter, text=text) 110 | return logdraft 111 | 112 | def start(self): 113 | self.daemon_drafter.start() 114 | 115 | def stop(self): 116 | self.daemon_drafter.stop() 117 | -------------------------------------------------------------------------------- /draftlog/loading.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | 3 | """ 4 | Please note that this is not actually part of the module "draftlog." It is just 5 | something cool I wrote and wanted to include. 6 | """ 7 | 8 | 9 | 10 | from colorconsole.terminal import get_terminal 11 | import time 12 | import sys 13 | import threading 14 | if sys.version_info[0] <= 2: 15 | import Queue as queue 16 | else: 17 | import queue 18 | 19 | class Loading(threading.Thread): 20 | def __init__(self, frames=None): 21 | # Frames takes each frame seperated by a space with the very last frame 22 | # being the "done" frame. 23 | 24 | # Initialize self into a threading object and start running it. 25 | super(Loading, self).__init__() 26 | self.text_queue = queue.Queue() 27 | self.setDaemon(True) 28 | 29 | # Internal variables 30 | if frames == None: 31 | self.change_frames("snake") 32 | else: 33 | self.frames = frames.split(" ") 34 | self.time = 0.03 35 | self.t = get_terminal() 36 | self.text = "" 37 | self.frame = 0 38 | sys.stdout.write("\x1b[7") 39 | 40 | def change_frames(self, key): 41 | # Valid types: dots, circles 42 | frames = { 43 | "snake": ("⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ ⠿", 0.03), 44 | "fatsnake": ("⣾ ⣽ ⣻ ⢿ ⡿ ⣟ ⣯ ⣷ ⣿", 0.1), 45 | "drumming": ("⠋ ⠙ ⠚ ⠞ ⠖ ⠦ ⠴ ⠲ ⠳ ⠓ ⠿", 0.03), 46 | "pouring": ("⠄ ⠆ ⠇ ⠋ ⠙ ⠸ ⠰ ⠠ ⠰ ⠸ ⠙ ⠋ ⠇ ⠆ ⠿", 0.05), 47 | "curls": ("⠋ ⠙ ⠚ ⠒ ⠂ ⠂ ⠒ ⠲ ⠴ ⠦ ⠖ ⠒ ⠐ ⠐ ⠒ ⠓ ⠋ ⠿", 0.05), 48 | "jumping": ("⢄ ⢂ ⢁ ⡁ ⡈ ⡐ ⡠ ⠿", 0.05), 49 | "flash": ("◯ ◉ ● ◉ ●", 0.2), 50 | "circles": ("◜ ◠ ◝ ◞ ◡ ◟ ◯", 0.1), 51 | "bars": ("▁ ▃ ▄ ▅ ▆ ▇ █ ▇ ▆ ▅ ▄ ▃ █", 0.1), 52 | "wheel": ("| / - \\ |", 0.3), 53 | "pulse": ("▉ ▊ ▋ ▌ ▍ ▎ ▏ ▎ ▍ ▌ ▋ ▊ ▉", 0.03), 54 | "arrows": ("← ↖ ↑ ↗ → ↘ ↓ ↙ ↑", 0.1), 55 | "pipes": ("┤ ┘ ┴ └ ├ ┌ ┬ ┐ ─", 0.1), 56 | "grow": (". o O ° O o O", 0.1), 57 | "evolve": (". o O @ * @ O o *", 0.1), 58 | "eyes": ("◡◡ ⊙⊙ ◠◠ ⊙⊙ ⊙⊙", 0.3), 59 | "trigram": ("☰ ☱ ☳ ☷ ☶ ☴ ☰ ☰ ☰", 0.1), 60 | "sphere": ("🌑 🌒 🌓 🌔 🌕 🌖 🌗 🌘 🌑", 0.1), 61 | "dot": ("⠁ ⠂ ⠄ ⡀ ⢀ ⠠ ⠐ ⠈ .", 0.05) 62 | } 63 | 64 | if frames.get(key) == None: 65 | raise KeyError("Not a valid type. Must be of type: %s" % frames.keys()) 66 | else: 67 | self.frames, self.time = frames.get(key) 68 | self.frames = self.frames.split(" ") 69 | 70 | def write_text(self, frame=None, text=None): 71 | if not frame: frame = self.frame 72 | if not text: text = self.text 73 | sys.stdout.write("\x1b[8") 74 | print (text.replace("%s", "{0}").format(self.frames[frame])) 75 | 76 | def color_frames(self, n): 77 | # cyan = 36; purple = 35; blue = 34; green = 32; yellow = 33; red = 31 78 | self.frames = ["\x1b[" + str(n) + "m\x1b[1m" + s + "\x1b[0m" for s in self.frames] 79 | 80 | def log(self, text): 81 | self.text_queue.put(text) 82 | 83 | def end(self, text=None): 84 | if text == None: text = self.text 85 | self.text_queue.put("quit") 86 | self.write_text(frame=-1, text=text) 87 | self.join() 88 | 89 | def run(self): 90 | while True: 91 | if not self.text_queue.empty(): 92 | self.text = self.text_queue.get() 93 | if self.text == "quit": 94 | break 95 | 96 | if self.text: 97 | if self.frame > len(self.frames) - 2: 98 | self.frame = 0 99 | self.write_text() 100 | self.t.move_up() 101 | time.sleep(self.time) 102 | self.frame += 1 103 | 104 | """ 105 | l = Loading() 106 | l.start() 107 | l.color_frames(36) 108 | l.log("%s" + "Loading THE THING".center(20) + "%s") 109 | time.sleep(3) 110 | l.change_frames(sys.argv[1]) 111 | l.color_frames(36) 112 | l.log("%s" + "Still loading".center(20) + "%s") 113 | time.sleep(3) 114 | l.end("%s" + "Done Loading".center(20) + "%s") 115 | """ --------------------------------------------------------------------------------