' % (self.path)
42 |
43 |
44 | def __getattribute__(self, name):
45 |
46 | if not name in ('_exists', 'path', '_create', '_raise_if_none'):
47 | if not self._exists:
48 | self._create()
49 | return object.__getattribute__(self, name)
50 |
51 |
52 | def _raise_if_none(self):
53 | """Raises if operations are carried out on an unconfigured AppDir."""
54 | if not self.path:
55 | raise NotConfigured()
56 |
57 |
58 | def _create(self):
59 | """Creates current AppDir at AppDir.path."""
60 |
61 | self._raise_if_none()
62 | if not self._exists:
63 | mkdir_p(self.path)
64 | self._exists = True
65 |
66 |
67 | def open(self, filename, mode='r'):
68 | """Returns file object from given filename."""
69 |
70 | self._raise_if_none()
71 | fn = path_join(self.path, filename)
72 |
73 | return open(fn, mode)
74 |
75 |
76 | def write(self, filename, content, binary=False):
77 | """Writes given content to given filename."""
78 | self._raise_if_none()
79 | fn = path_join(self.path, filename)
80 |
81 | if binary:
82 | flags = 'wb'
83 | else:
84 | flags = 'w'
85 |
86 |
87 | with open(fn, flags) as f:
88 | f.write(content)
89 |
90 |
91 | def append(self, filename, content, binary=False):
92 | """Appends given content to given filename."""
93 |
94 | self._raise_if_none()
95 | fn = path_join(self.path, filename)
96 |
97 | if binary:
98 | flags = 'ab'
99 | else:
100 | flags = 'a'
101 |
102 | with open(fn, 'a') as f:
103 | f.write(content)
104 | return True
105 |
106 | def delete(self, filename=''):
107 | """Deletes given file or directory. If no filename is passed, current
108 | directory is removed.
109 | """
110 | self._raise_if_none()
111 | fn = path_join(self.path, filename)
112 |
113 | try:
114 | if isfile(fn):
115 | remove(fn)
116 | else:
117 | removedirs(fn)
118 | except OSError as why:
119 | if why.errno == errno.ENOENT:
120 | pass
121 | else:
122 | raise why
123 |
124 |
125 | def read(self, filename, binary=False):
126 | """Returns contents of given file with AppDir.
127 | If file doesn't exist, returns None."""
128 |
129 | self._raise_if_none()
130 | fn = path_join(self.path, filename)
131 |
132 | if binary:
133 | flags = 'br'
134 | else:
135 | flags = 'r'
136 |
137 | try:
138 | with open(fn, flags) as f:
139 | return f.read()
140 | except IOError:
141 | return None
142 |
143 |
144 | def sub(self, path):
145 | """Returns AppDir instance for given subdirectory name."""
146 |
147 | if is_collection(path):
148 | path = path_join(path)
149 |
150 | return AppDir(path_join(self.path, path))
151 |
152 |
153 | # Module locals
154 |
155 | user = AppDir()
156 | site = AppDir()
157 | cache = AppDir()
158 | log = AppDir()
159 |
160 |
161 | def init(vendor, name):
162 |
163 | global user, site, cache, log
164 |
165 | ad = AppDirs(name, vendor)
166 |
167 | user.path = ad.user_data_dir
168 |
169 | site.path = ad.site_data_dir
170 | cache.path = ad.user_cache_dir
171 | log.path = ad.user_log_dir
172 |
173 |
174 | class NotConfigured(IOError):
175 | """Application configuration required. Please run resources.init() first."""
176 |
--------------------------------------------------------------------------------
/script/clint/packages/ordereddict.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2009 Raymond Hettinger
2 | #
3 | # Permission is hereby granted, free of charge, to any person
4 | # obtaining a copy of this software and associated documentation files
5 | # (the "Software"), to deal in the Software without restriction,
6 | # including without limitation the rights to use, copy, modify, merge,
7 | # publish, distribute, sublicense, and/or sell copies of the Software,
8 | # and to permit persons to whom the Software is furnished to do so,
9 | # subject to the following conditions:
10 | #
11 | # The above copyright notice and this permission notice shall be
12 | # included in all copies or substantial portions of the Software.
13 | #
14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
16 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
21 | # OTHER DEALINGS IN THE SOFTWARE.
22 |
23 | from UserDict import DictMixin
24 |
25 | class OrderedDict(dict, DictMixin):
26 |
27 | def __init__(self, *args, **kwds):
28 | if len(args) > 1:
29 | raise TypeError('expected at most 1 arguments, got %d' % len(args))
30 | try:
31 | self.__end
32 | except AttributeError:
33 | self.clear()
34 | self.update(*args, **kwds)
35 |
36 | def clear(self):
37 | self.__end = end = []
38 | end += [None, end, end] # sentinel node for doubly linked list
39 | self.__map = {} # key --> [key, prev, next]
40 | dict.clear(self)
41 |
42 | def __setitem__(self, key, value):
43 | if key not in self:
44 | end = self.__end
45 | curr = end[1]
46 | curr[2] = end[1] = self.__map[key] = [key, curr, end]
47 | dict.__setitem__(self, key, value)
48 |
49 | def __delitem__(self, key):
50 | dict.__delitem__(self, key)
51 | key, prev, next = self.__map.pop(key)
52 | prev[2] = next
53 | next[1] = prev
54 |
55 | def __iter__(self):
56 | end = self.__end
57 | curr = end[2]
58 | while curr is not end:
59 | yield curr[0]
60 | curr = curr[2]
61 |
62 | def __reversed__(self):
63 | end = self.__end
64 | curr = end[1]
65 | while curr is not end:
66 | yield curr[0]
67 | curr = curr[1]
68 |
69 | def popitem(self, last=True):
70 | if not self:
71 | raise KeyError('dictionary is empty')
72 | if last:
73 | key = reversed(self).next()
74 | else:
75 | key = iter(self).next()
76 | value = self.pop(key)
77 | return key, value
78 |
79 | def __reduce__(self):
80 | items = [[k, self[k]] for k in self]
81 | tmp = self.__map, self.__end
82 | del self.__map, self.__end
83 | inst_dict = vars(self).copy()
84 | self.__map, self.__end = tmp
85 | if inst_dict:
86 | return (self.__class__, (items,), inst_dict)
87 | return self.__class__, (items,)
88 |
89 | def keys(self):
90 | return list(self)
91 |
92 | setdefault = DictMixin.setdefault
93 | update = DictMixin.update
94 | pop = DictMixin.pop
95 | values = DictMixin.values
96 | items = DictMixin.items
97 | iterkeys = DictMixin.iterkeys
98 | itervalues = DictMixin.itervalues
99 | iteritems = DictMixin.iteritems
100 |
101 | def __repr__(self):
102 | if not self:
103 | return '%s()' % (self.__class__.__name__,)
104 | return '%s(%r)' % (self.__class__.__name__, self.items())
105 |
106 | def copy(self):
107 | return self.__class__(self)
108 |
109 | @classmethod
110 | def fromkeys(cls, iterable, value=None):
111 | d = cls()
112 | for key in iterable:
113 | d[key] = value
114 | return d
115 |
116 | def __eq__(self, other):
117 | if isinstance(other, OrderedDict):
118 | if len(self) != len(other):
119 | return False
120 | for p, q in zip(self.items(), other.items()):
121 | if p != q:
122 | return False
123 | return True
124 | return dict.__eq__(self, other)
125 |
126 | def __ne__(self, other):
127 | return not self == other
128 |
--------------------------------------------------------------------------------
/script/clint/textui/formatters.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | clint.textui.formatters
5 | ~~~~~~~~~~~~~~~~~~~~~~~
6 |
7 | Core TextUI functionality for text formatting.
8 |
9 | """
10 |
11 | from __future__ import absolute_import
12 | from contextlib import contextmanager
13 |
14 | from .colored import ColoredString, clean
15 | from ..utils import tsplit, schunk
16 |
17 |
18 | NEWLINES = ('\n', '\r', '\r\n')
19 | MAX_WIDTHS = []
20 |
21 |
22 | def min_width(string, cols, padding=' '):
23 | """Returns given string with right padding."""
24 |
25 | is_color = isinstance(string, ColoredString)
26 |
27 | stack = tsplit(str(string), NEWLINES)
28 |
29 | for i, substring in enumerate(stack):
30 | _sub = clean(substring).ljust((cols + 0), padding)
31 | if is_color:
32 | _sub = (_sub.replace(clean(substring), substring))
33 | stack[i] = _sub
34 |
35 | return '\n'.join(stack)
36 |
37 |
38 | def _get_max_width_context():
39 | return MAX_WIDTHS
40 |
41 | @contextmanager
42 | def _max_width_context():
43 | """Max width context manager."""
44 | try:
45 | yield
46 | finally:
47 | MAX_WIDTHS.pop()
48 |
49 | def max_width(*args, **kwargs):
50 | """Returns formatted text or context manager for textui:puts.
51 |
52 | >>> from clint.textui import puts, max_width
53 | >>> max_width('123 5678', 8)
54 | '123 5678'
55 | >>> max_width('123 5678', 7)
56 | '123 \n5678'
57 | >>> with max_width(7):
58 | ... puts('123 5678')
59 | '123 \n5678'
60 | """
61 | args = list(args)
62 |
63 | if not args:
64 | args.append(kwargs.get('string'))
65 | args.append(kwargs.get('cols'))
66 | args.append(kwargs.get('separator'))
67 | elif len(args) == 1:
68 | args.append(kwargs.get('cols'))
69 | args.append(kwargs.get('separator'))
70 | elif len(args) == 2:
71 | args.append(kwargs.get('separator'))
72 |
73 | string, cols, separator = args
74 | if separator is None:
75 | separator = '\n' # default value
76 | if cols is None:
77 | # cols should be specified vitally
78 | # because string can be specified at textui:puts function
79 | string, cols = cols, string
80 |
81 | if string is None:
82 | MAX_WIDTHS.append((cols, separator))
83 | return _max_width_context()
84 | else:
85 | return _max_width_formatter(string, cols, separator)
86 |
87 |
88 | def _max_width_formatter(string, cols, separator='\n'):
89 | """Returns a freshly formatted
90 | :param string: string to be formatted
91 | :type string: basestring or clint.textui.colored.ColoredString
92 | :param cols: max width the text to be formatted
93 | :type cols: int
94 | :param separator: separator to break rows
95 | :type separator: basestring
96 | """
97 |
98 | is_color = isinstance(string, ColoredString)
99 |
100 | if is_color:
101 | string_copy = string._new('')
102 | string = string.s
103 |
104 | stack = tsplit(string, NEWLINES)
105 |
106 | for i, substring in enumerate(stack):
107 | stack[i] = substring.split()
108 |
109 | _stack = []
110 |
111 | for row in stack:
112 | _row = ['',]
113 | _row_i = 0
114 |
115 | for word in row:
116 | if (len(_row[_row_i]) + len(word)) <= cols:
117 | _row[_row_i] += word
118 | _row[_row_i] += ' '
119 |
120 | elif len(word) > cols:
121 |
122 | # ensure empty row
123 | if len(_row[_row_i]):
124 | _row[_row_i] = _row[_row_i].rstrip()
125 | _row.append('')
126 | _row_i += 1
127 |
128 | chunks = schunk(word, cols)
129 | for i, chunk in enumerate(chunks):
130 | if not (i + 1) == len(chunks):
131 | _row[_row_i] += chunk
132 | _row[_row_i] = _row[_row_i].rstrip()
133 | _row.append('')
134 | _row_i += 1
135 | else:
136 | _row[_row_i] += chunk
137 | _row[_row_i] += ' '
138 | else:
139 | _row[_row_i] = _row[_row_i].rstrip()
140 | _row.append('')
141 | _row_i += 1
142 | _row[_row_i] += word
143 | _row[_row_i] += ' '
144 | else:
145 | _row[_row_i] = _row[_row_i].rstrip()
146 |
147 | _row = map(str, _row)
148 | _stack.append(separator.join(_row))
149 |
150 | _s = '\n'.join(_stack)
151 | if is_color:
152 | _s = string_copy._new(_s)
153 | return _s
154 |
--------------------------------------------------------------------------------
/script/clint/textui/colored.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | clint.colored
5 | ~~~~~~~~~~~~~
6 |
7 | This module provides a simple and elegant wrapper for colorama.
8 |
9 | """
10 |
11 |
12 | from __future__ import absolute_import
13 |
14 | import os
15 | import re
16 | import sys
17 |
18 | PY3 = sys.version_info[0] >= 3
19 |
20 | from ..packages import colorama
21 | from ..utils import basestring
22 |
23 | __all__ = (
24 | 'red', 'green', 'yellow', 'blue',
25 | 'black', 'magenta', 'cyan', 'white',
26 | 'clean', 'disable'
27 | )
28 |
29 | COLORS = __all__[:-2]
30 |
31 | if 'get_ipython' in dir():
32 | """
33 | when ipython is fired lot of variables like _oh, etc are used.
34 | There are so many ways to find current python interpreter is ipython.
35 | get_ipython is easiest is most appealing for readers to understand.
36 | """
37 | DISABLE_COLOR = True
38 | else:
39 | DISABLE_COLOR = False
40 |
41 |
42 | class ColoredString(object):
43 | """Enhanced string for __len__ operations on Colored output."""
44 | def __init__(self, color, s, always_color=False, bold=False):
45 | super(ColoredString, self).__init__()
46 | if not PY3 and isinstance(s, unicode):
47 | self.s = s.encode('utf-8')
48 | else:
49 | self.s = s
50 | self.color = color
51 | self.always_color = always_color
52 | self.bold = bold
53 | if os.environ.get('CLINT_FORCE_COLOR'):
54 | self.always_color = True
55 |
56 | def __getattr__(self, att):
57 | def func_help(*args, **kwargs):
58 | result = getattr(self.s, att)(*args, **kwargs)
59 | try:
60 | is_result_string = isinstance(result, basestring)
61 | except NameError:
62 | is_result_string = isinstance(result, str)
63 | if is_result_string:
64 | return self._new(result)
65 | elif isinstance(result, list):
66 | return [self._new(x) for x in result]
67 | else:
68 | return result
69 | return func_help
70 |
71 | @property
72 | def color_str(self):
73 | style = 'BRIGHT' if self.bold else 'NORMAL'
74 | c = '%s%s%s%s%s' % (getattr(colorama.Fore, self.color), getattr(colorama.Style, style), self.s, colorama.Fore.RESET, getattr(colorama.Style, 'NORMAL'))
75 |
76 | if self.always_color:
77 | return c
78 | elif sys.stdout.isatty() and not DISABLE_COLOR:
79 | return c
80 | else:
81 | return self.s
82 |
83 |
84 | def __len__(self):
85 | return len(self.s)
86 |
87 | def __repr__(self):
88 | return "<%s-string: '%s'>" % (self.color, self.s)
89 |
90 | def __unicode__(self):
91 | value = self.color_str
92 | if isinstance(value, bytes):
93 | return value.decode('utf8')
94 | return value
95 |
96 | if PY3:
97 | __str__ = __unicode__
98 | else:
99 | def __str__(self):
100 | return self.color_str
101 |
102 | def __iter__(self):
103 | return iter(self.color_str)
104 |
105 | def __add__(self, other):
106 | return str(self.color_str) + str(other)
107 |
108 | def __radd__(self, other):
109 | return str(other) + str(self.color_str)
110 |
111 | def __mul__(self, other):
112 | return (self.color_str * other)
113 |
114 | def _new(self, s):
115 | return ColoredString(self.color, s)
116 |
117 |
118 | def clean(s):
119 | strip = re.compile("([^-_a-zA-Z0-9!@#%&=,/'\";:~`\$\^\*\(\)\+\[\]\.\{\}\|\?\<\>\\]+|[^\s]+)")
120 | txt = strip.sub('', str(s))
121 |
122 | strip = re.compile(r'\[\d+m')
123 | txt = strip.sub('', txt)
124 |
125 | return txt
126 |
127 |
128 | def black(string, always=False, bold=False):
129 | return ColoredString('BLACK', string, always_color=always, bold=bold)
130 |
131 | def red(string, always=False, bold=False):
132 | return ColoredString('RED', string, always_color=always, bold=bold)
133 |
134 | def green(string, always=False, bold=False):
135 | return ColoredString('GREEN', string, always_color=always, bold=bold)
136 |
137 | def yellow(string, always=False, bold=False):
138 | return ColoredString('YELLOW', string, always_color=always, bold=bold)
139 |
140 | def blue(string, always=False, bold=False):
141 | return ColoredString('BLUE', string, always_color=always, bold=bold)
142 |
143 | def magenta(string, always=False, bold=False):
144 | return ColoredString('MAGENTA', string, always_color=always, bold=bold)
145 |
146 | def cyan(string, always=False, bold=False):
147 | return ColoredString('CYAN', string, always_color=always, bold=bold)
148 |
149 | def white(string, always=False, bold=False):
150 | return ColoredString('WHITE', string, always_color=always, bold=bold)
151 |
152 | def disable():
153 | """Disables colors."""
154 | global DISABLE_COLOR
155 |
156 | DISABLE_COLOR = True
157 |
--------------------------------------------------------------------------------
/script/clint/packages/colorama/win32.py:
--------------------------------------------------------------------------------
1 |
2 | # from winbase.h
3 | STDOUT = -11
4 | STDERR = -12
5 |
6 | try:
7 | from ctypes import windll
8 | except ImportError:
9 | windll = None
10 | SetConsoleTextAttribute = lambda *_: None
11 | else:
12 | from ctypes import (
13 | byref, Structure, c_char, c_short, c_uint32, c_ushort
14 | )
15 |
16 | handles = {
17 | STDOUT: windll.kernel32.GetStdHandle(STDOUT),
18 | STDERR: windll.kernel32.GetStdHandle(STDERR),
19 | }
20 |
21 | SHORT = c_short
22 | WORD = c_ushort
23 | DWORD = c_uint32
24 | TCHAR = c_char
25 |
26 | class COORD(Structure):
27 | """struct in wincon.h"""
28 | _fields_ = [
29 | ('X', SHORT),
30 | ('Y', SHORT),
31 | ]
32 |
33 | class SMALL_RECT(Structure):
34 | """struct in wincon.h."""
35 | _fields_ = [
36 | ("Left", SHORT),
37 | ("Top", SHORT),
38 | ("Right", SHORT),
39 | ("Bottom", SHORT),
40 | ]
41 |
42 | class CONSOLE_SCREEN_BUFFER_INFO(Structure):
43 | """struct in wincon.h."""
44 | _fields_ = [
45 | ("dwSize", COORD),
46 | ("dwCursorPosition", COORD),
47 | ("wAttributes", WORD),
48 | ("srWindow", SMALL_RECT),
49 | ("dwMaximumWindowSize", COORD),
50 | ]
51 | def __str__(self):
52 | return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % (
53 | self.dwSize.Y, self.dwSize.X
54 | , self.dwCursorPosition.Y, self.dwCursorPosition.X
55 | , self.wAttributes
56 | , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right
57 | , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X
58 | )
59 |
60 | def GetConsoleScreenBufferInfo(stream_id=STDOUT):
61 | handle = handles[stream_id]
62 | csbi = CONSOLE_SCREEN_BUFFER_INFO()
63 | success = windll.kernel32.GetConsoleScreenBufferInfo(
64 | handle, byref(csbi))
65 | # This fails when imported via setup.py when installing using 'pip'
66 | # presumably the fix is that running setup.py should not trigger all
67 | # this activity.
68 | # assert success
69 | return csbi
70 |
71 | def SetConsoleTextAttribute(stream_id, attrs):
72 | handle = handles[stream_id]
73 | return windll.kernel32.SetConsoleTextAttribute(handle, attrs)
74 |
75 | def SetConsoleCursorPosition(stream_id, position):
76 | position = COORD(*position)
77 | # If the position is out of range, do nothing.
78 | if position.Y <= 0 or position.X <= 0:
79 | return
80 | # Adjust for Windows' SetConsoleCursorPosition:
81 | # 1. being 0-based, while ANSI is 1-based.
82 | # 2. expecting (x,y), while ANSI uses (y,x).
83 | adjusted_position = COORD(position.Y - 1, position.X - 1)
84 | # Adjust for viewport's scroll position
85 | sr = GetConsoleScreenBufferInfo(STDOUT).srWindow
86 | adjusted_position.Y += sr.Top
87 | adjusted_position.X += sr.Left
88 | # Resume normal processing
89 | handle = handles[stream_id]
90 | success = windll.kernel32.SetConsoleCursorPosition(handle, adjusted_position)
91 | return success
92 |
93 | def FillConsoleOutputCharacter(stream_id, char, length, start):
94 | handle = handles[stream_id]
95 | char = TCHAR(char)
96 | length = DWORD(length)
97 | num_written = DWORD(0)
98 | # Note that this is hard-coded for ANSI (vs wide) bytes.
99 | success = windll.kernel32.FillConsoleOutputCharacterA(
100 | handle, char, length, start, byref(num_written))
101 | return num_written.value
102 |
103 | def FillConsoleOutputAttribute(stream_id, attr, length, start):
104 | ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )'''
105 | handle = handles[stream_id]
106 | attribute = WORD(attr)
107 | length = DWORD(length)
108 | num_written = DWORD(0)
109 | # Note that this is hard-coded for ANSI (vs wide) bytes.
110 | success = windll.kernel32.FillConsoleOutputAttribute(
111 | handle, attribute, length, start, byref(num_written))
112 | return success
113 |
114 |
115 | if __name__=='__main__':
116 | x = GetConsoleScreenBufferInfo(STDOUT)
117 | print(x)
118 | print('dwSize(height,width) = (%d,%d)' % (x.dwSize.Y, x.dwSize.X))
119 | print('dwCursorPosition(y,x) = (%d,%d)' % (x.dwCursorPosition.Y, x.dwCursorPosition.X))
120 | print('wAttributes(color) = %d = 0x%02x' % (x.wAttributes, x.wAttributes))
121 | print('srWindow(Top,Left)-(Bottom,Right) = (%d,%d)-(%d,%d)' % (x.srWindow.Top, x.srWindow.Left, x.srWindow.Bottom, x.srWindow.Right))
122 | print('dwMaximumWindowSize(maxHeight,maxWidth) = (%d,%d)' % (x.dwMaximumWindowSize.Y, x.dwMaximumWindowSize.X))
123 |
124 |
--------------------------------------------------------------------------------
/script/clint/textui/progress.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | clint.textui.progress
5 | ~~~~~~~~~~~~~~~~~
6 |
7 | This module provides the progressbar functionality.
8 |
9 | """
10 |
11 | from __future__ import absolute_import
12 |
13 | import sys
14 | import time
15 |
16 | STREAM = sys.stderr
17 |
18 | BAR_TEMPLATE = '%s[%s%s] %i/%i - %s\r'
19 | MILL_TEMPLATE = '%s %s %i/%i\r'
20 |
21 | DOTS_CHAR = '.'
22 | BAR_FILLED_CHAR = '#'
23 | BAR_EMPTY_CHAR = ' '
24 | MILL_CHARS = ['|', '/', '-', '\\']
25 |
26 | # How long to wait before recalculating the ETA
27 | ETA_INTERVAL = 1
28 | # How many intervals (excluding the current one) to calculate the simple moving
29 | # average
30 | ETA_SMA_WINDOW = 9
31 |
32 |
33 | class Bar(object):
34 | def __enter__(self):
35 | return self
36 |
37 | def __exit__(self, exc_type, exc_val, exc_tb):
38 | self.done()
39 | return False # we're not suppressing exceptions
40 |
41 | def __init__(self, label='', width=32, hide=None, empty_char=BAR_EMPTY_CHAR,
42 | filled_char=BAR_FILLED_CHAR, expected_size=None, every=1):
43 | self.label = label
44 | self.width = width
45 | self.hide = hide
46 | # Only show bar in terminals by default (better for piping, logging etc.)
47 | if hide is None:
48 | try:
49 | self.hide = not STREAM.isatty()
50 | except AttributeError: # output does not support isatty()
51 | self.hide = True
52 | self.empty_char = empty_char
53 | self.filled_char = filled_char
54 | self.expected_size = expected_size
55 | self.every = every
56 | self.start = time.time()
57 | self.ittimes = []
58 | self.eta = 0
59 | self.etadelta = time.time()
60 | self.etadisp = self.format_time(self.eta)
61 | self.last_progress = 0
62 | if (self.expected_size):
63 | self.show(0)
64 |
65 | def show(self, progress, count=None):
66 | if count is not None:
67 | self.expected_size = count
68 | if self.expected_size is None:
69 | raise Exception("expected_size not initialized")
70 | self.last_progress = progress
71 | if (time.time() - self.etadelta) > ETA_INTERVAL:
72 | self.etadelta = time.time()
73 | self.ittimes = \
74 | self.ittimes[-ETA_SMA_WINDOW:] + \
75 | [-(self.start - time.time()) / (progress+1)]
76 | self.eta = \
77 | sum(self.ittimes) / float(len(self.ittimes)) * \
78 | (self.expected_size - progress)
79 | self.etadisp = self.format_time(self.eta)
80 | x = int(self.width * progress / self.expected_size)
81 | if not self.hide:
82 | if ((progress % self.every) == 0 or # True every "every" updates
83 | (progress == self.expected_size)): # And when we're done
84 | STREAM.write(BAR_TEMPLATE % (
85 | self.label, self.filled_char * x,
86 | self.empty_char * (self.width - x), progress,
87 | self.expected_size, self.etadisp))
88 | STREAM.flush()
89 |
90 | def done(self):
91 | self.elapsed = time.time() - self.start
92 | elapsed_disp = self.format_time(self.elapsed)
93 | if not self.hide:
94 | # Print completed bar with elapsed time
95 | STREAM.write(BAR_TEMPLATE % (
96 | self.label, self.filled_char * self.width,
97 | self.empty_char * 0, self.last_progress,
98 | self.expected_size, elapsed_disp))
99 | STREAM.write('\n')
100 | STREAM.flush()
101 |
102 | def format_time(self, seconds):
103 | return time.strftime('%H:%M:%S', time.gmtime(seconds))
104 |
105 |
106 | def bar(it, label='', width=32, hide=None, empty_char=BAR_EMPTY_CHAR,
107 | filled_char=BAR_FILLED_CHAR, expected_size=None, every=1):
108 | """Progress iterator. Wrap your iterables with it."""
109 |
110 | count = len(it) if expected_size is None else expected_size
111 |
112 | with Bar(label=label, width=width, hide=hide, empty_char=empty_char,
113 | filled_char=filled_char, expected_size=count, every=every) \
114 | as bar:
115 | for i, item in enumerate(it):
116 | yield item
117 | bar.show(i + 1)
118 |
119 |
120 | def dots(it, label='', hide=None, every=1):
121 | """Progress iterator. Prints a dot for each item being iterated"""
122 |
123 | count = 0
124 |
125 | if not hide:
126 | STREAM.write(label)
127 |
128 | for i, item in enumerate(it):
129 | if not hide:
130 | if i % every == 0: # True every "every" updates
131 | STREAM.write(DOTS_CHAR)
132 | sys.stderr.flush()
133 |
134 | count += 1
135 |
136 | yield item
137 |
138 | STREAM.write('\n')
139 | STREAM.flush()
140 |
141 |
142 | def mill(it, label='', hide=None, expected_size=None, every=1):
143 | """Progress iterator. Prints a mill while iterating over the items."""
144 |
145 | def _mill_char(_i):
146 | if _i >= count:
147 | return ' '
148 | else:
149 | return MILL_CHARS[(_i // every) % len(MILL_CHARS)]
150 |
151 | def _show(_i):
152 | if not hide:
153 | if ((_i % every) == 0 or # True every "every" updates
154 | (_i == count)): # And when we're done
155 |
156 | STREAM.write(MILL_TEMPLATE % (
157 | label, _mill_char(_i), _i, count))
158 | STREAM.flush()
159 |
160 | count = len(it) if expected_size is None else expected_size
161 |
162 | if count:
163 | _show(0)
164 |
165 | for i, item in enumerate(it):
166 | yield item
167 | _show(i + 1)
168 |
169 | if not hide:
170 | STREAM.write('\n')
171 | STREAM.flush()
172 |
--------------------------------------------------------------------------------
/script/clint/textui/prompt.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf8 -*-
2 |
3 | """
4 | clint.textui.prompt
5 | ~~~~~~~~~~~~~~~~~~~
6 |
7 | Module for simple interactive prompts handling
8 |
9 | """
10 |
11 | from __future__ import absolute_import, print_function
12 |
13 | from re import match, I
14 | import getpass
15 |
16 | from .core import puts
17 | from .colored import yellow
18 | from .validators import RegexValidator, OptionValidator
19 |
20 | try:
21 | raw_input
22 | except NameError:
23 | raw_input = input
24 |
25 |
26 | def yn(prompt, default='y', batch=False):
27 | # A sanity check against default value
28 | # If not y/n then y is assumed
29 | if default not in ['y', 'n']:
30 | default = 'y'
31 |
32 | # Let's build the prompt
33 | choicebox = '[Y/n]' if default == 'y' else '[y/N]'
34 | prompt = prompt + ' ' + choicebox + ' '
35 |
36 | # If input is not a yes/no variant or empty
37 | # keep asking
38 | while True:
39 | # If batch option is True then auto reply
40 | # with default input
41 | if not batch:
42 | input = raw_input(prompt).strip()
43 | else:
44 | print(prompt)
45 | input = ''
46 |
47 | # If input is empty default choice is assumed
48 | # so we return True
49 | if input == '':
50 | return True
51 |
52 | # Given 'yes' as input if default choice is y
53 | # then return True, False otherwise
54 | if match('y(?:es)?', input, I):
55 | return True if default == 'y' else False
56 |
57 | # Given 'no' as input if default choice is n
58 | # then return True, False otherwise
59 | elif match('n(?:o)?', input, I):
60 | return True if default == 'n' else False
61 |
62 |
63 | def query(prompt, default='', validators=None, batch=False, mask_input=False):
64 | # Set the nonempty validator as default
65 | if validators is None:
66 | validators = [RegexValidator(r'.+')]
67 |
68 | # Let's build the prompt
69 | if prompt[-1] is not ' ':
70 | prompt += ' '
71 |
72 | if default:
73 | prompt += '[' + default + '] '
74 |
75 | # If input is not valid keep asking
76 | while True:
77 | # If batch option is True then auto reply
78 | # with default input
79 | if not batch:
80 | user_input_fn = getpass.getpass if mask_input else raw_input
81 | user_input = user_input_fn(prompt).strip() or default
82 | else:
83 | print(prompt)
84 | user_input = ''
85 |
86 | # Validate the user input
87 | try:
88 | for validator in validators:
89 | user_input = validator(user_input)
90 | return user_input
91 | except Exception as e:
92 | puts(yellow(e.message))
93 |
94 |
95 |
96 | def options(prompt, options, default=None, batch=False):
97 | '''
98 |
99 | :param prompt:
100 | :param options:
101 | this can be either a list of strings, in which case it will be presented like:
102 | prompt:
103 | (1) this is the first string
104 | (2) this is the second string
105 | (3) this is the third string
106 |
107 | please select 1-3:
108 |
109 | or a list of dictionaries in the format of:
110 | { { 'selector' : 'this is what the user will enter to select the option'
111 | 'prompt': 'this is the string that will be displayed, this can be omitted if the selector is also a prompt',
112 | 'return': 'this is what is returned to the calling procedure, if omitted, the option selector will be used' }
113 |
114 | so, to replicate the above, the dict could look like:
115 |
116 | [ {'selector':1,'prompt':'this is the first string','return':1},
117 | {'selector':2,'prompt':'this is the second string','return':2},
118 | {'selector':3,'prompt':'this is the third string'}
119 |
120 | :param default: should be set to the default selector (if desired)
121 | :param batch: True/False, will auto-return the default
122 | :return:
123 | '''
124 | # Build fix options and build validator
125 |
126 | validator_list = []
127 | return_dict = {}
128 |
129 | if isinstance(options[0],dict):
130 | for item in options:
131 | item['selector'] = str(item['selector'])
132 | item['prompt'] = str(item['prompt'])
133 | if 'return' not in item:
134 | item['return'] = item['selector']
135 | validator_list.append(item['selector'])
136 | return_dict[item['selector']] = item['return']
137 | else:
138 | options_strings = options
139 | options = []
140 | for key, opt in enumerate(options_strings):
141 | item = {}
142 | item['selector'] = str(key+1)
143 | item['prompt'] = str(opt)
144 | item['return'] = key+1
145 |
146 | return_dict[item['selector']] = item['return']
147 | validator_list.append(item['selector'])
148 | options.append(item)
149 |
150 | validators = [OptionValidator(validator_list)]
151 |
152 | # Let's build the prompt
153 |
154 | prompt += '\n'
155 |
156 | # building the options list
157 | for o in options:
158 | prompt += '[{selector}] {prompt}\n'.format(**o)
159 |
160 | prompt += '\n'
161 |
162 | if default:
163 | prompt += '[' + default + '] '
164 |
165 | # If input is not valid keep asking
166 | while True:
167 | # If batch option is True then auto reply
168 | # with default input
169 | if not batch:
170 | user_input = raw_input(prompt).strip() or default
171 | else:
172 | print(prompt)
173 | user_input = ''
174 |
175 |
176 | # Validate the user input
177 | try:
178 | for validator in validators:
179 | user_input = validator(user_input)
180 | # convert user input to defined return value
181 | user_input = return_dict[user_input]
182 | return user_input
183 | except Exception as e:
184 | puts(yellow(e.message))
185 |
--------------------------------------------------------------------------------
/GUIApp/Core.py:
--------------------------------------------------------------------------------
1 | from tkinter import *
2 | from tkinter import messagebox
3 | from tkinter.ttk import Progressbar
4 | import os
5 | import time
6 | import sys
7 | import json
8 | import requests
9 | from shutil import copy
10 |
11 |
12 | class WistiaDownloaderCore():
13 | def __init__(self, master, videos, resolution, downloadFolder, btnBorderX, btnBorderY):
14 | self.master = master
15 | self.videos = videos
16 | self.resolution = resolution
17 | self.resolution_list = ["224p", "360p", "540p", "720p", "Best Quality"]
18 | self.downloadFolder = downloadFolder
19 | self.URL = "http://fast.wistia.net/embed/iframe/"
20 | self.videoIndex = 1
21 | self.logs = ""
22 |
23 | self.btnBorderX = btnBorderX
24 | self.btnBorderY = btnBorderY
25 |
26 | if self.dataCheck():
27 | for video in self.videos:
28 | videoURL = self.URL + video.get('id')
29 | html_file = self.download_HTML_file(videoURL)
30 | self.html_page_handle_and_download(html_file, self.resolution, video.get('name'))
31 | try:
32 | self.downloadLable.configure(text='Download Completed')
33 | except:
34 | pass
35 | messagebox.showinfo(
36 | "Wistia Downloader", "Download completed, check logs for any error.")
37 |
38 | def dataCheck(self):
39 | if not self.videos:
40 | messagebox.showinfo(
41 | "Wistia Downloader", "You need to enter at least one ID.")
42 | return 0
43 | if not self.resolution in self.resolution_list:
44 | messagebox.showinfo(
45 | "Wistia Downloader", "You have to select a resolution from the list.")
46 | return 0
47 | if not self.downloadFolder:
48 | messagebox.showinfo(
49 | "Wistia Downloader", "You need to select the download folder.")
50 | return 0
51 | return 1
52 |
53 | def download_HTML_file(self, url):
54 | local_filename = url.split('/')[-1]
55 | r = requests.get(url, stream=True)
56 | with open(local_filename, 'wb') as f:
57 | for chunk in r.iter_content(chunk_size=1024):
58 | if chunk:
59 | f.write(chunk)
60 | return local_filename
61 |
62 | def html_page_handle_and_download(self, file_name, resolution, viedo_name):
63 | with open(file_name, "r") as file:
64 | i = 1
65 | for con in file:
66 | # ERROR
67 | if i == 1 and con == '{"error":true,"iframe":true}':
68 | video_data = con
69 | if i == 63:
70 | video_data = con
71 | break
72 | i += 1
73 | if video_data == '{"error":true,"iframe":true}':
74 | self.logs += '{} ID {}, is not valid.\n'.format(
75 | viedo_name,
76 | self.videoIndex)
77 | self.showLogs(self.logs)
78 | self.videoIndex += 1
79 | else:
80 | video_data = video_data.split('[')[1].split(']')[0]
81 | video_data = video_data.replace('},{', '}abdlalisalmi{')
82 | video_data_list = video_data.split('abdlalisalmi')
83 |
84 | video_url = None
85 | for video in video_data_list:
86 | video_data_json = json.loads(video)
87 | if video_data_json['display_name'] == resolution:
88 | video_url = video_data_json['url']
89 |
90 | self.showProgressBar()
91 | self.downloadLable.configure(
92 | text='{} is Downloading...'.format(viedo_name))
93 |
94 | if (video_url):
95 | video_file_name = self.download_video(video_url)
96 |
97 | video_name_and_index = "{}-{}".format(
98 | str(self.videoIndex), viedo_name)
99 | self.videoIndex += 1
100 | os.rename(video_file_name, video_name_and_index)
101 | else:
102 | video_url = json.loads(video_data_list[0])['url']
103 | print(json.loads(video_data_list[0]))
104 | video_file_name = self.download_video(video_url)
105 |
106 | video_name_and_index = "{}-{}".format(
107 | str(self.videoIndex), viedo_name)
108 | self.videoIndex += 1
109 | os.rename(video_file_name, video_name_and_index)
110 |
111 | os.remove(file_name)
112 |
113 | def download_video(self, url):
114 | video_name = url.split('/')[-1]
115 | reponse = requests.get(url, stream=True)
116 | with open(video_name, 'wb') as video:
117 | total_length = int(reponse.headers.get('content-length'))
118 | total_received = 0
119 | for chunk in reponse.iter_content(chunk_size=1024):
120 | if chunk:
121 | progress = ((total_received / total_length) * 100) + 1
122 | self.progressBar['value'] = progress
123 | self.progressLabel.configure(
124 | text='{}%'.format(int(progress)))
125 | self.progressBar.update()
126 |
127 | video.write(chunk)
128 | total_received += 1024
129 |
130 | return video_name
131 |
132 | def showProgressBar(self):
133 | self.progressBar = Progressbar(self.master, length=300)
134 | self.progressBar['value'] = 0
135 |
136 | self.downloadLable = Label(self.master, text="Downloading...",
137 | fg='#2c3e50', font=("Arial Bold", 9))
138 | self.downloadLable.place(
139 | x=self.btnBorderX + 10, y=self.btnBorderY + 187)
140 |
141 | self.progressLabel = Label(self.master, text='0%',
142 | fg='#2c3e50', font=("Arial Bold", 10))
143 | self.progressLabel.place(
144 | x=self.btnBorderX + 10, y=self.btnBorderY + 210)
145 | Label(self.master, text="100%",
146 | fg='#2c3e50', font=("Arial Bold", 10)).place(x=self.btnBorderX + 340, y=self.btnBorderY + 210)
147 |
148 | self.progressBar.place(x=self.btnBorderX + 40, y=self.btnBorderY + 210)
149 |
150 | def showLogs(self, error):
151 | self.errorLabel = Label(self.master, text='Logs:',
152 | fg='#2c3e50', font=("Arial Bold", 10))
153 | self.errorLabel.place(
154 | x=self.btnBorderX + 10, y=self.btnBorderY + 245)
155 |
156 | self.errorField = Text(self.master, background="#ff7979", borderwidth=2, height=3,
157 | width=52, font=("Arial Bold", 8))
158 | self.errorField.place(x=self.btnBorderX + 10, y=self.btnBorderY + 265)
159 | self.errorField.insert(
160 | END, error)
161 | self.errorField.config(state=DISABLED)
162 |
--------------------------------------------------------------------------------
/script/clint/packages/colorama/ansitowin32.py:
--------------------------------------------------------------------------------
1 |
2 | import re
3 | import sys
4 |
5 | from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style
6 | from .winterm import WinTerm, WinColor, WinStyle
7 | from .win32 import windll
8 |
9 |
10 | if windll is not None:
11 | winterm = WinTerm()
12 |
13 |
14 | def is_a_tty(stream):
15 | return hasattr(stream, 'isatty') and stream.isatty()
16 |
17 |
18 | class StreamWrapper(object):
19 | '''
20 | Wraps a stream (such as stdout), acting as a transparent proxy for all
21 | attribute access apart from method 'write()', which is delegated to our
22 | Converter instance.
23 | '''
24 | def __init__(self, wrapped, converter):
25 | # double-underscore everything to prevent clashes with names of
26 | # attributes on the wrapped stream object.
27 | self.__wrapped = wrapped
28 | self.__convertor = converter
29 |
30 | def __getattr__(self, name):
31 | return getattr(self.__wrapped, name)
32 |
33 | def write(self, text):
34 | self.__convertor.write(text)
35 |
36 |
37 | class AnsiToWin32(object):
38 | '''
39 | Implements a 'write()' method which, on Windows, will strip ANSI character
40 | sequences from the text, and if outputting to a tty, will convert them into
41 | win32 function calls.
42 | '''
43 | ANSI_RE = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
44 |
45 | def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
46 | # The wrapped stream (normally sys.stdout or sys.stderr)
47 | self.wrapped = wrapped
48 |
49 | # should we reset colors to defaults after every .write()
50 | self.autoreset = autoreset
51 |
52 | # create the proxy wrapping our output stream
53 | self.stream = StreamWrapper(wrapped, self)
54 |
55 | on_windows = sys.platform.startswith('win')
56 |
57 | # should we strip ANSI sequences from our output?
58 | if strip is None:
59 | strip = on_windows
60 | self.strip = strip
61 |
62 | # should we should convert ANSI sequences into win32 calls?
63 | if convert is None:
64 | convert = on_windows and is_a_tty(wrapped)
65 | self.convert = convert
66 |
67 | # dict of ansi codes to win32 functions and parameters
68 | self.win32_calls = self.get_win32_calls()
69 |
70 | # are we wrapping stderr?
71 | self.on_stderr = self.wrapped is sys.stderr
72 |
73 |
74 | def should_wrap(self):
75 | '''
76 | True if this class is actually needed. If false, then the output
77 | stream will not be affected, nor will win32 calls be issued, so
78 | wrapping stdout is not actually required. This will generally be
79 | False on non-Windows platforms, unless optional functionality like
80 | autoreset has been requested using kwargs to init()
81 | '''
82 | return self.convert or self.strip or self.autoreset
83 |
84 |
85 | def get_win32_calls(self):
86 | if self.convert and winterm:
87 | return {
88 | AnsiStyle.RESET_ALL: (winterm.reset_all, ),
89 | AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),
90 | AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),
91 | AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),
92 | AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),
93 | AnsiFore.RED: (winterm.fore, WinColor.RED),
94 | AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),
95 | AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),
96 | AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),
97 | AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),
98 | AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),
99 | AnsiFore.WHITE: (winterm.fore, WinColor.GREY),
100 | AnsiFore.RESET: (winterm.fore, ),
101 | AnsiBack.BLACK: (winterm.back, WinColor.BLACK),
102 | AnsiBack.RED: (winterm.back, WinColor.RED),
103 | AnsiBack.GREEN: (winterm.back, WinColor.GREEN),
104 | AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),
105 | AnsiBack.BLUE: (winterm.back, WinColor.BLUE),
106 | AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),
107 | AnsiBack.CYAN: (winterm.back, WinColor.CYAN),
108 | AnsiBack.WHITE: (winterm.back, WinColor.GREY),
109 | AnsiBack.RESET: (winterm.back, ),
110 | }
111 |
112 |
113 | def write(self, text):
114 | if self.strip or self.convert:
115 | self.write_and_convert(text)
116 | else:
117 | self.wrapped.write(text)
118 | self.wrapped.flush()
119 | if self.autoreset:
120 | self.reset_all()
121 |
122 |
123 | def reset_all(self):
124 | if self.convert:
125 | self.call_win32('m', (0,))
126 | elif is_a_tty(self.wrapped):
127 | self.wrapped.write(Style.RESET_ALL)
128 |
129 |
130 | def write_and_convert(self, text):
131 | '''
132 | Write the given text to our wrapped stream, stripping any ANSI
133 | sequences from the text, and optionally converting them into win32
134 | calls.
135 | '''
136 | cursor = 0
137 | for match in self.ANSI_RE.finditer(text):
138 | start, end = match.span()
139 | self.write_plain_text(text, cursor, start)
140 | self.convert_ansi(*match.groups())
141 | cursor = end
142 | self.write_plain_text(text, cursor, len(text))
143 |
144 |
145 | def write_plain_text(self, text, start, end):
146 | if start < end:
147 | self.wrapped.write(text[start:end])
148 | self.wrapped.flush()
149 |
150 |
151 | def convert_ansi(self, paramstring, command):
152 | if self.convert:
153 | params = self.extract_params(paramstring)
154 | self.call_win32(command, params)
155 |
156 |
157 | def extract_params(self, paramstring):
158 | def split(paramstring):
159 | for p in paramstring.split(';'):
160 | if p != '':
161 | yield int(p)
162 | return tuple(split(paramstring))
163 |
164 |
165 | def call_win32(self, command, params):
166 | if params == []:
167 | params = [0]
168 | if command == 'm':
169 | for param in params:
170 | if param in self.win32_calls:
171 | func_args = self.win32_calls[param]
172 | func = func_args[0]
173 | args = func_args[1:]
174 | kwargs = dict(on_stderr=self.on_stderr)
175 | func(*args, **kwargs)
176 | elif command in ('H', 'f'): # set cursor position
177 | func = winterm.set_cursor_position
178 | func(params, on_stderr=self.on_stderr)
179 | elif command in ('J'):
180 | func = winterm.erase_data
181 | func(params, on_stderr=self.on_stderr)
182 |
183 |
--------------------------------------------------------------------------------
/script/wistia-downloader.py:
--------------------------------------------------------------------------------
1 | import os
2 | import time
3 | import sys
4 | import json
5 | import requests
6 | import getpass
7 | from shutil import copy
8 | from clint.textui import progress # for the progress effects
9 |
10 | video_index = 1
11 |
12 |
13 | def logo():
14 | sprint("""\033[1;33;40m
15 | _ _ _ _ _ _____
16 | | || || (_) _ (_) (____ \\
17 | | || || |_ ___| |_ _ ____ _ \ \ ___ _ _ _ ____
18 | | ||_|| | |/___) _)| |/ _ | | | | / _ \| | | | _ \\
19 | | |___| | |___ | |__| ( ( | | | |__/ / |_| | | | | | | |
20 | \______|_(___/ \___)_|\_||_| |_____/ \___/ \____|_| |_|
21 | https://github.com/abdlalisalmi
22 | \033[0;37;40m""", 0.1)
23 |
24 |
25 | def sprint(string, tm=10):
26 | for c in string + '\n':
27 | sys.stdout.write(c)
28 | sys.stdout.flush()
29 | time.sleep(tm / 100)
30 |
31 |
32 | def download_video(url):
33 | video_name = url.split('/')[-1]
34 | reponse = requests.get(url, stream=True)
35 | with open(video_name, 'wb') as video:
36 | total_length = int(reponse.headers.get('content-length'))
37 | for chunk in progress.bar(reponse.iter_content(chunk_size=1024), expected_size=(total_length/1024) + 1):
38 | if chunk:
39 | video.write(chunk)
40 | video.flush()
41 | return video_name
42 |
43 |
44 | def html_page_handle_and_download(file_name, resolution):
45 | print("\033[1;32;40m")
46 | global video_index
47 | with open(file_name, "r") as file:
48 | i = 1
49 | for con in file:
50 | # ERROR
51 | if i == 1 and con == '{"error":true,"iframe":true}':
52 | video_data = con
53 | if i == 63:
54 | video_data = con
55 | break
56 | i += 1
57 | if video_data == '{"error":true,"iframe":true}':
58 | sprint("\033[1;31;40mYour video id is not valid\033[0;37;40m", 2)
59 | else:
60 | video_data = video_data.split('[')[1].split(']')[0]
61 | video_data = video_data.replace('},{', '}abdlalisalmi{')
62 | video_data_list = video_data.split('abdlalisalmi')
63 |
64 | resolution_dic = {
65 | '1': '224p',
66 | '2': '360p',
67 | '3': '540p',
68 | '4': '720p',
69 | '5': 'Original file'
70 | }
71 | video_url = None
72 | for video in video_data_list:
73 | video_data_json = json.loads(video)
74 | if video_data_json['display_name'] == resolution_dic[str(resolution)]:
75 | video_url = video_data_json['url']
76 | if (video_url):
77 | video_name = download_video(video_url)
78 |
79 | video_name_and_index = "Video{}.mp4".format(str(video_index))
80 | video_index += 1
81 | os.rename(video_name, video_name_and_index)
82 | else:
83 | print(
84 | "\033[1;31;40mThe resulotion ({}) does not exist for this video, we will download it with the original resolution.\033[0;37;40m".format(resolution_dic[str(resolution)]))
85 | video_url = json.loads(video_data_list[0])['url']
86 | print(json.loads(video_data_list[0]))
87 | video_name = download_video(video_url)
88 |
89 | video_name_and_index = "Video{}.mp4".format(str(video_index))
90 | video_index += 1
91 | os.rename(video_name, video_name_and_index)
92 |
93 | os.remove(file_name)
94 | print("\033[0;37;40m")
95 |
96 |
97 | def download_folder():
98 | try:
99 | username = getpass.getuser()
100 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
101 |
102 | sprint(
103 | "\033[1;32;40mPlease enter the PATH of download folder: \033[0;37;40m", 2)
104 | sprint(
105 | "\033[1;31;40m\t\tExample: \033[0;37;40m/Users/{}/Desktop".format(username), 2)
106 | sprint(
107 | "\033[1;31;40m\t\tExample: \033[0;37;40m/Volumes/Storage/goinfre/{}".format(username), 2)
108 | sprint(
109 | "\033[1;31;40m\t\tExample: \033[0;37;40m{}".format(BASE_DIR), 2)
110 |
111 | folder_path = input("PATH: \033[1;33;40m")
112 | print("\033[0;37;40m")
113 |
114 | slash = '/' if '/' in folder_path else '\\'
115 | if folder_path[-1] == slash:
116 | folder_path += "wistia-downloader"
117 | else:
118 | folder_path += slash + "wistia-downloader"
119 |
120 | os.mkdir(folder_path)
121 | copy('video_ID.txt', folder_path)
122 |
123 | os.chdir("{}".format(folder_path))
124 | sprint("\033[1;33;40mYou will find the videos in {}\033[0;37;40m".format(
125 | folder_path), 2)
126 |
127 | except FileNotFoundError:
128 | print("\033[1;31;40mThe PATH does not exist !! try again\033[0;37;40m")
129 | sprint(".....", 100)
130 | main()
131 | except FileExistsError:
132 | print(
133 | "\033[1;31;40mThe wistia-downloader folder is exist !! try another PATH\033[0;37;40m")
134 | sprint(".....", 100)
135 | main()
136 |
137 |
138 | def resolution_select():
139 | resolution = 0
140 | print('')
141 | sprint(
142 | "\033[1;32;40mPlease select download resolution:\033[0;37;40m", 2)
143 | sprint(
144 | "\033[1;31;40m\t\t1) \033[0;37;40m224p", 2)
145 | sprint(
146 | "\033[1;31;40m\t\t2) \033[0;37;40m360p", 2)
147 | sprint(
148 | "\033[1;31;40m\t\t3) \033[0;37;40m540p", 2)
149 | sprint(
150 | "\033[1;31;40m\t\t4) \033[0;37;40m720p", 2)
151 | sprint(
152 | "\033[1;31;40m\t\t5) \033[0;37;40mBest Quality", 2)
153 | choices = ['1', '2', '3', '4', '5']
154 | while not resolution in choices:
155 | resolution = input('> ')
156 | if not resolution in choices:
157 | print('Please select a number between 1 and 5.')
158 | return int(resolution)
159 |
160 |
161 | def download_file(url):
162 | local_filename = url.split('/')[-1]
163 | r = requests.get(url, stream=True)
164 | with open(local_filename, 'wb') as f:
165 | for chunk in r.iter_content(chunk_size=1024):
166 | if chunk:
167 | f.write(chunk)
168 | return local_filename
169 |
170 |
171 | def main():
172 | os.system('cls' if os.name == 'nt' else 'clear')
173 | logo()
174 |
175 | url = "http://fast.wistia.net/embed/iframe/"
176 |
177 | i = 0
178 | with open("video_ID.txt", "r+") as video_id:
179 | if os.stat("video_ID.txt").st_size == 0:
180 | print("")
181 | sprint(
182 | "\033[1;31;40mThe file video_ID.txt is empty !!\033[0;37;40m", 0.2)
183 | sprint(".....", 100)
184 | else:
185 | start = time.time()
186 | download_folder()
187 | resolution = resolution_select()
188 |
189 | for id in video_id:
190 |
191 | if '\n' in id:
192 | new_id = id[:-1]
193 | else:
194 | new_id = id
195 |
196 | video_url = url + new_id
197 | local_filename = download_file(video_url)
198 | print("Video ID " + str(i + 1) +
199 | "--------------------------------------------------------------------------")
200 | html_page_handle_and_download(
201 | local_filename, resolution)
202 | i += 1
203 |
204 | end = time.time()
205 | sprint(
206 | "\033[1;33;40mDownload finished, Total time {}\033[0;37;40m".format(end - start), 2)
207 |
208 |
209 | if __name__ == '__main__':
210 | main()
211 |
--------------------------------------------------------------------------------
/GUIApp/GUI.py:
--------------------------------------------------------------------------------
1 | from tkinter import *
2 | from tkinter import messagebox
3 | from tkinter.ttk import Combobox
4 | from tkinter import filedialog
5 | from tkinter.ttk import Progressbar
6 | import webbrowser
7 |
8 |
9 | import os
10 | import sys
11 | import time
12 |
13 | from Core import WistiaDownloaderCore
14 |
15 |
16 | class WistiaDownloaderGUI():
17 | def __init__(self, master, screen_width, screen_height):
18 | self.master = master
19 |
20 | self.screen_width = screen_width
21 | self.screen_height = screen_height
22 |
23 | self.borderX = int(screen_width * 0.06)
24 | self.borderY = int(screen_width - (screen_width * 0.06))
25 |
26 | self.btnBorderX = int(self.screen_width / 2.5)
27 | self.btnBorderY = int(self.screen_height / 5)
28 |
29 | self.videos = []
30 |
31 | self.IDsList = []
32 | self.resolution = "224p"
33 | self.downloadFolder = None
34 |
35 | self.showLogo()
36 | self.showTextInput()
37 | self.showImmport()
38 | self.showResolutionSelect()
39 | self.showDownloadFolderSelect()
40 | self.showDownloadBtn()
41 | self.showLogs()
42 | self.showProgressBar()
43 | self.showHelpBtn()
44 | self.showQuitBtn()
45 |
46 | def showLogo(self):
47 | Label(self.master, text="Wistia Downloader ",
48 | fg='#574b90', font=('Helvetica', 30)).place(x=int(self.screen_width/3.6), y=15)
49 | Label(self.master, text="https://github.com/abdlalisalmi",
50 | fg='#786fa6', font=("Arial Bold", 10)).place(x=int(self.screen_width/3.6) + 5, y=55)
51 |
52 | def showImmport(self):
53 | # Importing the IDs from a file
54 | def importIDsFromFile():
55 | filePATH = filedialog.askopenfilename(
56 | initialdir=os.path.dirname(os.path.realpath(__file__)),
57 | title="Select file",
58 | filetypes=(("text files", "*.txt"),)
59 | )
60 | if filePATH:
61 | with open(filePATH, 'r') as f:
62 |
63 | content = f.read().split('\n')
64 | content = list(filter(None, content))
65 | self.video_data_handle(content)
66 |
67 | i = 1
68 | str_content = ""
69 | for video in self.videos:
70 | str_content += '{}-{}'.format(i, video['name']) + '\n'
71 | i = i + 1
72 | self.textArea.config(state=NORMAL)
73 | self.textArea.insert(END, str_content)
74 | self.textArea.config(state=DISABLED)
75 |
76 | Button(
77 | self.master, font=("Arial Bold", 8),
78 | fg="white", bg='#5799cf',
79 | text="Import", command=importIDsFromFile
80 | ).place(x=self.borderX + 130, y=122)
81 |
82 | def showTextInput(self):
83 |
84 | self.showBorder(self.borderX, self.btnBorderY+10, width=25, height=2)
85 |
86 | self.textInputLable = Label(self.master, text="Import Videos IDs File:",
87 | fg='#303952', font=("Arial Bold", 10))
88 | self.textInputLable.place(x=self.borderX, y=self.btnBorderY)
89 |
90 | self.textArea = Text(self.master, bg='#dff9fb', borderwidth=2, height=19,
91 | width=25, font=("Arial Bold", 9))
92 | self.textArea.place(x=self.borderX, y=160)
93 | self.textArea.config(state=DISABLED)
94 |
95 | def showBorder(self, x, y, height=2, width=48):
96 | Label(self.master,
97 | borderwidth=2,
98 | width=width,
99 | height=height,
100 | relief="ridge",).place(x=x, y=y)
101 |
102 | def showResolutionSelect(self):
103 | self.showBorder(self.btnBorderX, self.btnBorderY+10)
104 | resolutionLable = Label(self.master,
105 | text="Select The Resolution:",
106 | fg='#303952', font=("Arial Bold", 10))
107 | resolutionLable.place(x=self.btnBorderX, y=self.btnBorderY)
108 |
109 | self.resolutions_list = {"224p": "224p",
110 | "360p": "360p",
111 | "540p": "540p",
112 | "720p": "720p",
113 | "Best Quality": "Best Quality"}
114 |
115 | def choiceClicked():
116 | self.resolution = choice.get()
117 | choice = StringVar(self.master, "224p")
118 | x = self.btnBorderX + 6
119 | for (resolution, value) in self.resolutions_list.items():
120 | Radiobutton(self.master,
121 | text=resolution,
122 | variable=choice,
123 | value=value,
124 | command=choiceClicked
125 | ).place(x=x, y=self.btnBorderY + 20)
126 | x = x + 65
127 |
128 | def showDownloadFolderSelect(self):
129 | self.showBorder(self.btnBorderX, y=self.btnBorderY + 75)
130 |
131 | def selectDownloadFolder():
132 | self.downloadFolder = filedialog.askdirectory()
133 | try:
134 | self.downloadPATH.destroy()
135 | except:
136 | pass
137 | self.downloadPATH = Label(self.master, text=self.downloadFolder,
138 | fg='#303952', font=("Arial Bold", 7))
139 | self.downloadPATH.place(
140 | x=self.btnBorderX + 5, y=self.btnBorderY + 90)
141 |
142 | folderLable = Label(self.master, text="Select The Download Folder:",
143 | fg='#2c3e50', font=("Arial Bold", 10))
144 | folderLable.place(x=self.btnBorderX, y=self.btnBorderY + 65)
145 |
146 | self.selectDownloadFolderBtn = Button(
147 | self.master, font=("Arial Bold", 9),
148 | fg="white", bg='#6a89cc', text="Browse",
149 | command=selectDownloadFolder
150 | )
151 | self.selectDownloadFolderBtn.place(
152 | x=self.btnBorderX + 310, y=self.btnBorderY + 80)
153 |
154 | def showDownloadBtn(self):
155 | self.showBorder(self.btnBorderX, y=self.btnBorderY + 140, height=10)
156 |
157 | Label(self.master, text="Download:",
158 | fg='#303952', font=("Arial Bold", 10)
159 | ).place(x=self.btnBorderX, y=self.btnBorderY + 130)
160 | self.downloadBtn = Button(
161 | self.master, text="Start Download",
162 | fg="white", bg='#079992',
163 | command=self.download
164 | )
165 | self.downloadBtn.place(x=self.btnBorderX + 253,
166 | y=self.btnBorderY + 150)
167 |
168 | def showQuitBtn(self):
169 | frame = Frame(self.master)
170 | frame.place(x=int(self.screen_width - (self.borderX * 2)),
171 | y=int(self.screen_height - (self.borderX * 1.2)))
172 | self.quit = Button(frame,
173 | text="QUIT", fg="white", bg='#ff3f34',
174 | command=frame.quit)
175 | self.quit.grid(row=0, column=0)
176 |
177 | def showHelpBtn(self):
178 | def openweb():
179 | url = 'https://github.com/abdlalisalmi/wistia-downloader/blob/master/README.md'
180 | webbrowser.open(url)
181 |
182 | frame = Frame(self.master)
183 | frame.place(x=int(self.screen_width - (self.borderX * 4)),
184 | y=int(self.screen_height - (self.borderX * 1.2)))
185 | self.quit = Button(frame,
186 | text="Help", fg="white", bg='#ff793f',
187 | command=openweb)
188 | self.quit.grid(row=0, column=0)
189 |
190 | # the function called when we click on Start Download button
191 | def download(self):
192 | self.downloadBtn['state'] = DISABLED
193 | core = WistiaDownloaderCore(
194 | self.master, self.videos,
195 | self.resolution, self.downloadFolder,
196 | self.btnBorderX, self.btnBorderY,
197 | )
198 | self.downloadBtn['state'] = NORMAL
199 |
200 | def showProgressBar(self):
201 | self.progressBar = Progressbar(self.master, length=300)
202 | self.progressBar['value'] = 0
203 |
204 | self.progressLabel = Label(self.master, text='0%',
205 | fg='#2c3e50', font=("Arial Bold", 10))
206 | self.progressLabel.place(
207 | x=self.btnBorderX + 10, y=self.btnBorderY + 210)
208 | Label(self.master, text="100%",
209 | fg='#2c3e50', font=("Arial Bold", 10)).place(x=self.btnBorderX + 340, y=self.btnBorderY + 210)
210 |
211 | self.progressBar.place(x=self.btnBorderX + 40, y=self.btnBorderY + 210)
212 |
213 | def showLogs(self):
214 | self.logsLabel = Label(self.master, text='Logs:',
215 | fg='#2c3e50', font=("Arial Bold", 10))
216 | self.logsLabel.place(
217 | x=self.btnBorderX + 10, y=self.btnBorderY + 245)
218 |
219 | self.logsField = Text(self.master, background='#d1d8e0', borderwidth=2, height=3,
220 | width=52, font=("Arial Bold", 8))
221 | self.logsField.place(x=self.btnBorderX + 10, y=self.btnBorderY + 265)
222 | self.logsField.config(state=DISABLED)
223 |
224 | def video_data_handle(self, content):
225 | try:
226 | for line in content:
227 | line = line.split('')[2]
228 | video_id = line.split('"')[1].split('=')[1]
229 | video_name = line.split('>')[1].split('<')[0]
230 | self.videos.append({'id': video_id, 'name': video_name})
231 | except:
232 | messagebox.showinfo(
233 | "Wistia Downloader", "You Need To Enter a Valid Videos File IDs.")
--------------------------------------------------------------------------------
/script/clint/arguments.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """
4 | clint.arguments
5 | ~~~~~~~~~~~~~~~
6 |
7 | This module provides the CLI argument interface.
8 |
9 | """
10 |
11 |
12 | from __future__ import absolute_import
13 |
14 | import os
15 | from sys import argv
16 |
17 | try:
18 | from collections import OrderedDict
19 | except ImportError:
20 | from .packages.ordereddict import OrderedDict
21 |
22 | from .utils import expand_path, is_collection
23 |
24 | __all__ = ('Args', )
25 |
26 |
27 | class Args(object):
28 | """CLI Argument management."""
29 |
30 | def __init__(self, args=None, no_argv=False):
31 | if not args:
32 | if not no_argv:
33 | self._args = argv[1:]
34 | else:
35 | self._args = []
36 | else:
37 | self._args = args
38 |
39 |
40 | def __len__(self):
41 | return len(self._args)
42 |
43 |
44 | def __repr__(self):
45 | return '' % (repr(self._args))
46 |
47 |
48 | def __getitem__(self, i):
49 | try:
50 | return self.all[i]
51 | except IndexError:
52 | return None
53 |
54 |
55 | def __contains__(self, x):
56 | return self.first(x) is not None
57 |
58 |
59 | def get(self, x):
60 | """Returns argument at given index, else none."""
61 | try:
62 | return self.all[x]
63 | except IndexError:
64 | return None
65 |
66 |
67 | def get_with(self, x):
68 | """Returns first argument that contains given string."""
69 | return self.all[self.first_with(x)]
70 |
71 |
72 | def remove(self, x):
73 | """Removes given arg (or list thereof) from Args object."""
74 |
75 | def _remove(x):
76 | found = self.first(x)
77 | if found is not None:
78 | self._args.pop(found)
79 |
80 | if is_collection(x):
81 | for item in x:
82 | _remove(x)
83 | else:
84 | _remove(x)
85 |
86 |
87 | def pop(self, x):
88 | """Removes and Returns value at given index, else none."""
89 | try:
90 | return self._args.pop(x)
91 | except IndexError:
92 | return None
93 |
94 |
95 | def any_contain(self, x):
96 | """Tests if given string is contained in any stored argument."""
97 |
98 | return bool(self.first_with(x))
99 |
100 |
101 | def contains(self, x):
102 | """Tests if given object is in arguments list.
103 | Accepts strings and lists of strings."""
104 |
105 | return self.__contains__(x)
106 |
107 |
108 | def first(self, x):
109 | """Returns first found index of given value (or list of values)"""
110 |
111 | def _find( x):
112 | try:
113 | return self.all.index(str(x))
114 | except ValueError:
115 | return None
116 |
117 | if is_collection(x):
118 | for item in x:
119 | found = _find(item)
120 | if found is not None:
121 | return found
122 | return None
123 | else:
124 | return _find(x)
125 |
126 |
127 | def first_with(self, x):
128 | """Returns first found index containing value (or list of values)"""
129 |
130 | def _find(x):
131 | try:
132 | for arg in self.all:
133 | if x in arg:
134 | return self.all.index(arg)
135 | except ValueError:
136 | return None
137 |
138 | if is_collection(x):
139 | for item in x:
140 | found = _find(item)
141 | if found:
142 | return found
143 | return None
144 | else:
145 | return _find(x)
146 |
147 |
148 | def first_without(self, x):
149 | """Returns first found index not containing value (or list of values)"""
150 |
151 | def _find(x):
152 | try:
153 | for arg in self.all:
154 | if x not in arg:
155 | return self.all.index(arg)
156 | except ValueError:
157 | return None
158 |
159 | if is_collection(x):
160 | for item in x:
161 | found = _find(item)
162 | if found:
163 | return found
164 | return None
165 | else:
166 | return _find(x)
167 |
168 |
169 | def start_with(self, x):
170 | """Returns all arguments beginning with given string (or list thereof)"""
171 |
172 | _args = []
173 |
174 | for arg in self.all:
175 | if is_collection(x):
176 | for _x in x:
177 | if arg.startswith(x):
178 | _args.append(arg)
179 | break
180 | else:
181 | if arg.startswith(x):
182 | _args.append(arg)
183 |
184 | return Args(_args, no_argv=True)
185 |
186 |
187 | def contains_at(self, x, index):
188 | """Tests if given [list of] string is at given index."""
189 |
190 | try:
191 | if is_collection(x):
192 | for _x in x:
193 | if (_x in self.all[index]) or (_x == self.all[index]):
194 | return True
195 | else:
196 | return False
197 | else:
198 | return (x in self.all[index])
199 |
200 | except IndexError:
201 | return False
202 |
203 |
204 | def has(self, x):
205 | """Returns true if argument exists at given index.
206 | Accepts: integer.
207 | """
208 |
209 | try:
210 | self.all[x]
211 | return True
212 | except IndexError:
213 | return False
214 |
215 |
216 | def value_after(self, x):
217 | """Returns value of argument after given found argument (or list thereof)."""
218 |
219 | try:
220 | try:
221 | i = self.all.index(x)
222 | except ValueError:
223 | return None
224 |
225 | return self.all[i + 1]
226 |
227 | except IndexError:
228 | return None
229 |
230 |
231 | @property
232 | def grouped(self):
233 | """Extracts --flag groups from argument list.
234 | Returns {format: Args, ...}
235 | """
236 |
237 | collection = OrderedDict(_=Args(no_argv=True))
238 |
239 | _current_group = None
240 |
241 | for arg in self.all:
242 | if arg.startswith('-'):
243 | _current_group = arg
244 | collection.setdefault(arg, Args(no_argv=True))
245 | else:
246 | if _current_group:
247 | collection[_current_group]._args.append(arg)
248 | else:
249 | collection['_']._args.append(arg)
250 |
251 | return collection
252 |
253 |
254 | @property
255 | def last(self):
256 | """Returns last argument."""
257 |
258 | try:
259 | return self.all[-1]
260 | except IndexError:
261 | return None
262 |
263 |
264 | @property
265 | def all(self):
266 | """Returns all arguments."""
267 |
268 | return self._args
269 |
270 |
271 | def all_with(self, x):
272 | """Returns all arguments containing given string (or list thereof)"""
273 |
274 | _args = []
275 |
276 | for arg in self.all:
277 | if is_collection(x):
278 | for _x in x:
279 | if _x in arg:
280 | _args.append(arg)
281 | break
282 | else:
283 | if x in arg:
284 | _args.append(arg)
285 |
286 | return Args(_args, no_argv=True)
287 |
288 |
289 | def all_without(self, x):
290 | """Returns all arguments not containing given string (or list thereof)"""
291 |
292 | _args = []
293 |
294 | for arg in self.all:
295 | if is_collection(x):
296 | for _x in x:
297 | if _x not in arg:
298 | _args.append(arg)
299 | break
300 | else:
301 | if x not in arg:
302 | _args.append(arg)
303 |
304 | return Args(_args, no_argv=True)
305 |
306 |
307 | @property
308 | def flags(self):
309 | """Returns Arg object including only flagged arguments."""
310 |
311 | return self.start_with('-')
312 |
313 |
314 | @property
315 | def not_flags(self):
316 | """Returns Arg object excluding flagged arguments."""
317 |
318 | return self.all_without('-')
319 |
320 |
321 | @property
322 | def files(self, absolute=False):
323 | """Returns an expanded list of all valid paths that were passed in."""
324 |
325 | _paths = []
326 |
327 | for arg in self.all:
328 | for path in expand_path(arg):
329 | if os.path.exists(path):
330 | if absolute:
331 | _paths.append(os.path.abspath(path))
332 | else:
333 | _paths.append(path)
334 |
335 | return _paths
336 |
337 |
338 | @property
339 | def not_files(self):
340 | """Returns a list of all arguments that aren't files/globs."""
341 |
342 | _args = []
343 |
344 | for arg in self.all:
345 | if not len(expand_path(arg)):
346 | if not os.path.exists(arg):
347 | _args.append(arg)
348 |
349 | return Args(_args, no_argv=True)
350 |
351 | @property
352 | def copy(self):
353 | """Returns a copy of Args object for temporary manipulation."""
354 |
355 | return Args(self.all)
356 |
357 |
--------------------------------------------------------------------------------
/script/clint/packages/appdirs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (c) 2005-2010 ActiveState Software Inc.
3 |
4 | """Utilities for determining application-specific dirs.
5 |
6 | See for details and usage.
7 | """
8 | # Dev Notes:
9 | # - MSDN on where to store app data files:
10 | # http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
11 | # - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
12 | # - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
13 |
14 | __version_info__ = (1, 2, 0)
15 | __version__ = '.'.join(map(str, __version_info__))
16 |
17 |
18 | import sys
19 | import os
20 |
21 | PY3 = sys.version_info[0] == 3
22 |
23 | if PY3:
24 | unicode = str
25 |
26 | class AppDirsError(Exception):
27 | pass
28 |
29 |
30 |
31 | def user_data_dir(appname, appauthor=None, version=None, roaming=False):
32 | r"""Return full path to the user-specific data dir for this application.
33 |
34 | "appname" is the name of application.
35 | "appauthor" (only required and used on Windows) is the name of the
36 | appauthor or distributing body for this application. Typically
37 | it is the owning company name.
38 | "version" is an optional version path element to append to the
39 | path. You might want to use this if you want multiple versions
40 | of your app to be able to run independently. If used, this
41 | would typically be ".".
42 | "roaming" (boolean, default False) can be set True to use the Windows
43 | roaming appdata directory. That means that for users on a Windows
44 | network setup for roaming profiles, this user data will be
45 | sync'd on login. See
46 |
47 | for a discussion of issues.
48 |
49 | Typical user data directories are:
50 | Mac OS X: ~/Library/Application Support/
51 | Unix: ~/.config/ # or in $XDG_CONFIG_HOME if defined
52 | Win XP (not roaming): C:\Documents and Settings\\Application Data\\
53 | Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\
54 | Win 7 (not roaming): C:\Users\\AppData\Local\\
55 | Win 7 (roaming): C:\Users\\AppData\Roaming\\
56 |
57 | For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. We don't
58 | use $XDG_DATA_HOME as that data dir is mostly used at the time of
59 | installation, instead of the application adding data during runtime.
60 | Also, in practice, Linux apps tend to store their data in
61 | "~/.config/" instead of "~/.local/share/".
62 | """
63 | if sys.platform.startswith("win"):
64 | if appauthor is None:
65 | raise AppDirsError("must specify 'appauthor' on Windows")
66 | const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
67 | path = os.path.join(_get_win_folder(const), appauthor, appname)
68 | elif sys.platform == 'darwin':
69 | path = os.path.join(
70 | os.path.expanduser('~/Library/Application Support/'),
71 | appname)
72 | else:
73 | path = os.path.join(
74 | os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")),
75 | appname.lower())
76 | if version:
77 | path = os.path.join(path, version)
78 | return path
79 |
80 |
81 | def site_data_dir(appname, appauthor=None, version=None):
82 | """Return full path to the user-shared data dir for this application.
83 |
84 | "appname" is the name of application.
85 | "appauthor" (only required and used on Windows) is the name of the
86 | appauthor or distributing body for this application. Typically
87 | it is the owning company name.
88 | "version" is an optional version path element to append to the
89 | path. You might want to use this if you want multiple versions
90 | of your app to be able to run independently. If used, this
91 | would typically be ".".
92 |
93 | Typical user data directories are:
94 | Mac OS X: /Library/Application Support/
95 | Unix: /etc/xdg/
96 | Win XP: C:\Documents and Settings\All Users\Application Data\\
97 | Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
98 | Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7.
99 |
100 | For Unix, this is using the $XDG_CONFIG_DIRS[0] default.
101 |
102 | WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
103 | """
104 | if sys.platform.startswith("win"):
105 | if appauthor is None:
106 | raise AppDirsError("must specify 'appauthor' on Windows")
107 | path = os.path.join(_get_win_folder("CSIDL_COMMON_APPDATA"),
108 | appauthor, appname)
109 | elif sys.platform == 'darwin':
110 | path = os.path.join(
111 | os.path.expanduser('/Library/Application Support'),
112 | appname)
113 | else:
114 | # XDG default for $XDG_CONFIG_DIRS[0]. Perhaps should actually
115 | # *use* that envvar, if defined.
116 | path = "/etc/xdg/"+appname.lower()
117 | if version:
118 | path = os.path.join(path, version)
119 | return path
120 |
121 |
122 | def user_cache_dir(appname, appauthor=None, version=None, opinion=True):
123 | r"""Return full path to the user-specific cache dir for this application.
124 |
125 | "appname" is the name of application.
126 | "appauthor" (only required and used on Windows) is the name of the
127 | appauthor or distributing body for this application. Typically
128 | it is the owning company name.
129 | "version" is an optional version path element to append to the
130 | path. You might want to use this if you want multiple versions
131 | of your app to be able to run independently. If used, this
132 | would typically be ".".
133 | "opinion" (boolean) can be False to disable the appending of
134 | "Cache" to the base app data dir for Windows. See
135 | discussion below.
136 |
137 | Typical user cache directories are:
138 | Mac OS X: ~/Library/Caches/
139 | Unix: ~/.cache/ (XDG default)
140 | Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache
141 | Vista: C:\Users\\AppData\Local\\\Cache
142 |
143 | On Windows the only suggestion in the MSDN docs is that local settings go in
144 | the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming
145 | app data dir (the default returned by `user_data_dir` above). Apps typically
146 | put cache data somewhere *under* the given dir here. Some examples:
147 | ...\Mozilla\Firefox\Profiles\\Cache
148 | ...\Acme\SuperApp\Cache\1.0
149 | OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
150 | This can be disabled with the `opinion=False` option.
151 | """
152 | if sys.platform.startswith("win"):
153 | if appauthor is None:
154 | raise AppDirsError("must specify 'appauthor' on Windows")
155 | path = os.path.join(_get_win_folder("CSIDL_LOCAL_APPDATA"),
156 | appauthor, appname)
157 | if opinion:
158 | path = os.path.join(path, "Cache")
159 | elif sys.platform == 'darwin':
160 | path = os.path.join(
161 | os.path.expanduser('~/Library/Caches'),
162 | appname)
163 | else:
164 | path = os.path.join(
165 | os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')),
166 | appname.lower())
167 | if version:
168 | path = os.path.join(path, version)
169 | return path
170 |
171 | def user_log_dir(appname, appauthor=None, version=None, opinion=True):
172 | r"""Return full path to the user-specific log dir for this application.
173 |
174 | "appname" is the name of application.
175 | "appauthor" (only required and used on Windows) is the name of the
176 | appauthor or distributing body for this application. Typically
177 | it is the owning company name.
178 | "version" is an optional version path element to append to the
179 | path. You might want to use this if you want multiple versions
180 | of your app to be able to run independently. If used, this
181 | would typically be ".".
182 | "opinion" (boolean) can be False to disable the appending of
183 | "Logs" to the base app data dir for Windows, and "log" to the
184 | base cache dir for Unix. See discussion below.
185 |
186 | Typical user cache directories are:
187 | Mac OS X: ~/Library/Logs/
188 | Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined
189 | Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs
190 | Vista: C:\Users\\AppData\Local\\\Logs
191 |
192 | On Windows the only suggestion in the MSDN docs is that local settings
193 | go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in
194 | examples of what some windows apps use for a logs dir.)
195 |
196 | OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA`
197 | value for Windows and appends "log" to the user cache dir for Unix.
198 | This can be disabled with the `opinion=False` option.
199 | """
200 | if sys.platform == "darwin":
201 | path = os.path.join(
202 | os.path.expanduser('~/Library/Logs'),
203 | appname)
204 | elif sys.platform == "win32":
205 | path = user_data_dir(appname, appauthor, version); version=False
206 | if opinion:
207 | path = os.path.join(path, "Logs")
208 | else:
209 | path = user_cache_dir(appname, appauthor, version); version=False
210 | if opinion:
211 | path = os.path.join(path, "log")
212 | if version:
213 | path = os.path.join(path, version)
214 | return path
215 |
216 |
217 | class AppDirs(object):
218 | """Convenience wrapper for getting application dirs."""
219 | def __init__(self, appname, appauthor, version=None, roaming=False):
220 | self.appname = appname
221 | self.appauthor = appauthor
222 | self.version = version
223 | self.roaming = roaming
224 | @property
225 | def user_data_dir(self):
226 | return user_data_dir(self.appname, self.appauthor,
227 | version=self.version, roaming=self.roaming)
228 | @property
229 | def site_data_dir(self):
230 | return site_data_dir(self.appname, self.appauthor,
231 | version=self.version)
232 | @property
233 | def user_cache_dir(self):
234 | return user_cache_dir(self.appname, self.appauthor,
235 | version=self.version)
236 | @property
237 | def user_log_dir(self):
238 | return user_log_dir(self.appname, self.appauthor,
239 | version=self.version)
240 |
241 |
242 |
243 |
244 | #---- internal support stuff
245 |
246 | def _get_win_folder_from_registry(csidl_name):
247 | """This is a fallback technique at best. I'm not sure if using the
248 | registry for this guarantees us the correct answer for all CSIDL_*
249 | names.
250 | """
251 | import _winreg
252 |
253 | shell_folder_name = {
254 | "CSIDL_APPDATA": "AppData",
255 | "CSIDL_COMMON_APPDATA": "Common AppData",
256 | "CSIDL_LOCAL_APPDATA": "Local AppData",
257 | }[csidl_name]
258 |
259 | key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
260 | r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
261 | dir, type = _winreg.QueryValueEx(key, shell_folder_name)
262 | return dir
263 |
264 | def _get_win_folder_with_pywin32(csidl_name):
265 | from win32com.shell import shellcon, shell
266 | dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
267 | # Try to make this a unicode path because SHGetFolderPath does
268 | # not return unicode strings when there is unicode data in the
269 | # path.
270 | try:
271 | dir = unicode(dir)
272 |
273 | # Downgrade to short path name if have highbit chars. See
274 | # .
275 | has_high_char = False
276 | for c in dir:
277 | if ord(c) > 255:
278 | has_high_char = True
279 | break
280 | if has_high_char:
281 | try:
282 | import win32api
283 | dir = win32api.GetShortPathName(dir)
284 | except ImportError:
285 | pass
286 | except UnicodeError:
287 | pass
288 | return dir
289 |
290 | def _get_win_folder_with_ctypes(csidl_name):
291 | import ctypes
292 |
293 | csidl_const = {
294 | "CSIDL_APPDATA": 26,
295 | "CSIDL_COMMON_APPDATA": 35,
296 | "CSIDL_LOCAL_APPDATA": 28,
297 | }[csidl_name]
298 |
299 | buf = ctypes.create_unicode_buffer(1024)
300 | ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
301 |
302 | # Downgrade to short path name if have highbit chars. See
303 | # .
304 | has_high_char = False
305 | for c in buf:
306 | if ord(c) > 255:
307 | has_high_char = True
308 | break
309 | if has_high_char:
310 | buf2 = ctypes.create_unicode_buffer(1024)
311 | if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
312 | buf = buf2
313 |
314 | return buf.value
315 |
316 | if sys.platform == "win32":
317 | try:
318 | import win32com.shell
319 | _get_win_folder = _get_win_folder_with_pywin32
320 | except ImportError:
321 | try:
322 | import ctypes
323 | _get_win_folder = _get_win_folder_with_ctypes
324 | except ImportError:
325 | _get_win_folder = _get_win_folder_from_registry
326 |
327 |
328 |
329 | #---- self test code
330 |
331 | if __name__ == "__main__":
332 | appname = "MyApp"
333 | appauthor = "MyCompany"
334 |
335 | props = ("user_data_dir", "site_data_dir", "user_cache_dir",
336 | "user_log_dir")
337 |
338 | print("-- app dirs (without optional 'version')")
339 | dirs = AppDirs(appname, appauthor, version="1.0")
340 | for prop in props:
341 | print("%s: %s" % (prop, getattr(dirs, prop)))
342 |
343 | print("\n-- app dirs (with optional 'version')")
344 | dirs = AppDirs(appname, appauthor)
345 | for prop in props:
346 | print("%s: %s" % (prop, getattr(dirs, prop)))
347 |
--------------------------------------------------------------------------------