mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-17 19:48:31 +02:00
Merge branch 'pr/211' into 2.0-rc1
This commit is contained in:
commit
800a373462
7 changed files with 138 additions and 56 deletions
|
@ -4,6 +4,10 @@ Changelog
|
||||||
|
|
||||||
### 1.8 (May 22, 2014)
|
### 1.8 (May 22, 2014)
|
||||||
|
|
||||||
|
* __1.8.4__ Improved: using external editors (thanks to @chrissexton)
|
||||||
|
* __1.8.3__ Fixed: export to text files and improves help (thanks to @igniteflow and @mpe)
|
||||||
|
* __1.8.2__ Better integration with environment variables (thanks to @ajaam and @matze)
|
||||||
|
* __1.8.1__ Minor bug fixes
|
||||||
* __1.8.0__ Official support for python 3.4
|
* __1.8.0__ Official support for python 3.4
|
||||||
|
|
||||||
### 1.7 (December 22, 2013)
|
### 1.7 (December 22, 2013)
|
||||||
|
|
|
@ -48,6 +48,15 @@ Text export
|
||||||
|
|
||||||
Pretty-prints your entire journal.
|
Pretty-prints your entire journal.
|
||||||
|
|
||||||
|
XML export
|
||||||
|
-----------
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
jrnl --export xml
|
||||||
|
|
||||||
|
Why anyone would want to export stuff to XML is beyond me, but here you go.
|
||||||
|
|
||||||
Export to files
|
Export to files
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
|
@ -79,26 +79,3 @@ class Entry:
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {
|
|
||||||
'title': self.title,
|
|
||||||
'body': self.body,
|
|
||||||
'date': self.date.strftime("%Y-%m-%d"),
|
|
||||||
'time': self.date.strftime("%H:%M"),
|
|
||||||
'starred': self.starred
|
|
||||||
}
|
|
||||||
|
|
||||||
def to_md(self):
|
|
||||||
date_str = self.date.strftime(self.journal.config['timeformat'])
|
|
||||||
body_wrapper = "\n\n" if self.body else ""
|
|
||||||
body = body_wrapper + self.body
|
|
||||||
space = "\n"
|
|
||||||
md_head = "###"
|
|
||||||
|
|
||||||
return u"{md} {date}, {title} {body} {space}".format(
|
|
||||||
md=md_head,
|
|
||||||
date=date_str,
|
|
||||||
title=self.title,
|
|
||||||
body=body,
|
|
||||||
space=space
|
|
||||||
)
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ jrnl is a simple journal application for your command line.
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
__title__ = 'jrnl'
|
__title__ = 'jrnl'
|
||||||
__version__ = '1.8.1'
|
__version__ = '1.8.4'
|
||||||
__author__ = 'Manuel Ebert'
|
__author__ = 'Manuel Ebert'
|
||||||
__license__ = 'MIT License'
|
__license__ = 'MIT License'
|
||||||
__copyright__ = 'Copyright 2013 - 2014 Manuel Ebert'
|
__copyright__ = 'Copyright 2013 - 2014 Manuel Ebert'
|
||||||
|
|
22
jrnl/cli.py
22
jrnl/cli.py
|
@ -153,17 +153,6 @@ def run(manual_args=None):
|
||||||
touch_journal(config['journal'])
|
touch_journal(config['journal'])
|
||||||
mode_compose, mode_export = guess_mode(args, config)
|
mode_compose, mode_export = guess_mode(args, config)
|
||||||
|
|
||||||
# open journal file or folder
|
|
||||||
if os.path.isdir(config['journal']):
|
|
||||||
if config['journal'].strip("/").endswith(".dayone") or \
|
|
||||||
"entries" in os.listdir(config['journal']):
|
|
||||||
journal = DayOneJournal.DayOne(**config)
|
|
||||||
else:
|
|
||||||
util.prompt(u"[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']))
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
journal = Journal.Journal(journal_name, **config)
|
|
||||||
|
|
||||||
# How to quit writing?
|
# How to quit writing?
|
||||||
if "win32" in sys.platform:
|
if "win32" in sys.platform:
|
||||||
_exit_multiline_code = "on a blank line, press Ctrl+Z and then Enter"
|
_exit_multiline_code = "on a blank line, press Ctrl+Z and then Enter"
|
||||||
|
@ -183,6 +172,17 @@ def run(manual_args=None):
|
||||||
else:
|
else:
|
||||||
mode_compose = False
|
mode_compose = False
|
||||||
|
|
||||||
|
# open journal file or folder
|
||||||
|
if os.path.isdir(config['journal']):
|
||||||
|
if config['journal'].strip("/").endswith(".dayone") or \
|
||||||
|
"entries" in os.listdir(config['journal']):
|
||||||
|
journal = DayOneJournal.DayOne(**config)
|
||||||
|
else:
|
||||||
|
util.prompt(u"[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']))
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
journal = Journal.Journal(journal_name, **config)
|
||||||
|
|
||||||
# Writing mode
|
# Writing mode
|
||||||
if mode_compose:
|
if mode_compose:
|
||||||
raw = " ".join(args.text).strip()
|
raw = " ".join(args.text).strip()
|
||||||
|
|
|
@ -6,6 +6,7 @@ import os
|
||||||
import json
|
import json
|
||||||
from .util import u, slugify
|
from .util import u, slugify
|
||||||
import codecs
|
import codecs
|
||||||
|
from xml.dom import minidom
|
||||||
|
|
||||||
|
|
||||||
def get_tags_count(journal):
|
def get_tags_count(journal):
|
||||||
|
@ -14,12 +15,12 @@ def get_tags_count(journal):
|
||||||
# I came across this construction, worry not and embrace the ensuing moment of enlightment.
|
# I came across this construction, worry not and embrace the ensuing moment of enlightment.
|
||||||
tags = [tag
|
tags = [tag
|
||||||
for entry in journal.entries
|
for entry in journal.entries
|
||||||
for tag in set(entry.tags)
|
for tag in set(entry.tags)]
|
||||||
]
|
|
||||||
# To be read: [for entry in journal.entries: for tag in set(entry.tags): tag]
|
# To be read: [for entry in journal.entries: for tag in set(entry.tags): tag]
|
||||||
tag_counts = set([(tags.count(tag), tag) for tag in tags])
|
tag_counts = set([(tags.count(tag), tag) for tag in tags])
|
||||||
return tag_counts
|
return tag_counts
|
||||||
|
|
||||||
|
|
||||||
def to_tag_list(journal):
|
def to_tag_list(journal):
|
||||||
"""Prints a list of all tags and the number of occurrences."""
|
"""Prints a list of all tags and the number of occurrences."""
|
||||||
tag_counts = get_tags_count(journal)
|
tag_counts = get_tags_count(journal)
|
||||||
|
@ -32,15 +33,82 @@ def to_tag_list(journal):
|
||||||
result += "\n".join(u"{0:20} : {1}".format(tag, n) for n, tag in sorted(tag_counts, reverse=True))
|
result += "\n".join(u"{0:20} : {1}".format(tag, n) for n, tag in sorted(tag_counts, reverse=True))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def entry_to_dict(entry):
|
||||||
|
return {
|
||||||
|
'title': entry.title,
|
||||||
|
'body': entry.body,
|
||||||
|
'date': entry.date.strftime("%Y-%m-%d"),
|
||||||
|
'time': entry.date.strftime("%H:%M"),
|
||||||
|
'starred': entry.starred
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def to_json(journal):
|
def to_json(journal):
|
||||||
"""Returns a JSON representation of the Journal."""
|
"""Returns a JSON representation of the Journal."""
|
||||||
tags = get_tags_count(journal)
|
tags = get_tags_count(journal)
|
||||||
result = {
|
result = {
|
||||||
"tags": dict((tag, count) for count, tag in tags),
|
"tags": dict((tag, count) for count, tag in tags),
|
||||||
"entries": [e.to_dict() for e in journal.entries]
|
"entries": [entry_to_dict(e) for e in journal.entries]
|
||||||
}
|
}
|
||||||
return json.dumps(result, indent=2)
|
return json.dumps(result, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
def entry_to_xml(entry, doc=None):
|
||||||
|
"""Turns an entry into an XML representation.
|
||||||
|
If doc is not given, it will return a full XML document.
|
||||||
|
Otherwise, it will only return a new 'entry' elemtent for
|
||||||
|
a given doc."""
|
||||||
|
doc_el = doc or minidom.Document()
|
||||||
|
entry_el = doc_el.createElement('entry')
|
||||||
|
for key, value in entry_to_dict(entry).items():
|
||||||
|
elem = doc_el.createElement(key)
|
||||||
|
elem.appendChild(doc_el.createTextNode(u(value)))
|
||||||
|
entry_el.appendChild(elem)
|
||||||
|
if not doc:
|
||||||
|
doc_el.appendChild(entry_el)
|
||||||
|
return doc_el.toprettyxml()
|
||||||
|
else:
|
||||||
|
return entry_el
|
||||||
|
|
||||||
|
|
||||||
|
def to_xml(journal):
|
||||||
|
"""Returns a XML representation of the Journal."""
|
||||||
|
tags = get_tags_count(journal)
|
||||||
|
doc = minidom.Document()
|
||||||
|
xml = doc.createElement('journal')
|
||||||
|
tags_el = doc.createElement('tags')
|
||||||
|
entries_el = doc.createElement('entries')
|
||||||
|
for tag in tags:
|
||||||
|
tag_el = doc.createElement('tag')
|
||||||
|
tag_el.setAttribute('name', tag[1])
|
||||||
|
count_node = doc.createTextNode(u(tag[0]))
|
||||||
|
tag.appendChild(count_node)
|
||||||
|
tags_el.appendChild(tag)
|
||||||
|
for entry in journal.entries:
|
||||||
|
entries_el.appendChild(entry_to_xml(entry, doc))
|
||||||
|
xml.appendChild(entries_el)
|
||||||
|
xml.appendChild(tags_el)
|
||||||
|
doc.appendChild(xml)
|
||||||
|
return doc.toprettyxml()
|
||||||
|
|
||||||
|
|
||||||
|
def entry_to_md(entry):
|
||||||
|
date_str = entry.date.strftime(entry.journal.config['timeformat'])
|
||||||
|
body_wrapper = "\n\n" if entry.body else ""
|
||||||
|
body = body_wrapper + entry.body
|
||||||
|
space = "\n"
|
||||||
|
md_head = "###"
|
||||||
|
|
||||||
|
return u"{md} {date}, {title} {body} {space}".format(
|
||||||
|
md=md_head,
|
||||||
|
date=date_str,
|
||||||
|
title=entry.title,
|
||||||
|
body=body,
|
||||||
|
space=space
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_md(journal):
|
def to_md(journal):
|
||||||
"""Returns a markdown representation of the Journal"""
|
"""Returns a markdown representation of the Journal"""
|
||||||
out = []
|
out = []
|
||||||
|
@ -58,24 +126,29 @@ def to_md(journal):
|
||||||
result = "\n".join(out)
|
result = "\n".join(out)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def to_txt(journal):
|
def to_txt(journal):
|
||||||
"""Returns the complete text of the Journal."""
|
"""Returns the complete text of the Journal."""
|
||||||
return journal.pprint()
|
return journal.pprint()
|
||||||
|
|
||||||
|
|
||||||
def export(journal, format, output=None):
|
def export(journal, format, output=None):
|
||||||
"""Exports the journal to various formats.
|
"""Exports the journal to various formats.
|
||||||
format should be one of json, txt, text, md, markdown.
|
format should be one of json, xml, txt, text, md, markdown.
|
||||||
If output is None, returns a unicode representation of the output.
|
If output is None, returns a unicode representation of the output.
|
||||||
If output is a directory, exports entries into individual files.
|
If output is a directory, exports entries into individual files.
|
||||||
Otherwise, exports to the given output file.
|
Otherwise, exports to the given output file.
|
||||||
"""
|
"""
|
||||||
maps = {
|
maps = {
|
||||||
"json": to_json,
|
"json": to_json,
|
||||||
|
"xml": to_xml,
|
||||||
"txt": to_txt,
|
"txt": to_txt,
|
||||||
"text": to_txt,
|
"text": to_txt,
|
||||||
"md": to_md,
|
"md": to_md,
|
||||||
"markdown": to_md
|
"markdown": to_md
|
||||||
}
|
}
|
||||||
|
if format not in maps:
|
||||||
|
return u"[ERROR: can't export to '{0}'. Valid options are 'md', 'txt', 'xml', and 'json']".format(format)
|
||||||
if output and os.path.isdir(output): # multiple files
|
if output and os.path.isdir(output): # multiple files
|
||||||
return write_files(journal, output, format)
|
return write_files(journal, output, format)
|
||||||
else:
|
else:
|
||||||
|
@ -84,24 +157,27 @@ def export(journal, format, output=None):
|
||||||
try:
|
try:
|
||||||
with codecs.open(output, "w", "utf-8") as f:
|
with codecs.open(output, "w", "utf-8") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
return "[Journal exported to {0}]".format(output)
|
return u"[Journal exported to {0}]".format(output)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
return "[ERROR: {0} {1}]".format(e.filename, e.strerror)
|
return u"[ERROR: {0} {1}]".format(e.filename, e.strerror)
|
||||||
else:
|
else:
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
def write_files(journal, path, format):
|
def write_files(journal, path, format):
|
||||||
"""Turns your journal into separate files for each entry.
|
"""Turns your journal into separate files for each entry.
|
||||||
Format should be either json, md or txt."""
|
Format should be either json, xml, md or txt."""
|
||||||
make_filename = lambda entry: e.date.strftime("%C-%m-%d_{0}.{1}".format(slugify(u(e.title)), format))
|
make_filename = lambda entry: e.date.strftime("%C-%m-%d_{0}.{1}".format(slugify(u(e.title)), format))
|
||||||
for e in journal.entries:
|
for e in journal.entries:
|
||||||
full_path = os.path.join(path, make_filename(e))
|
full_path = os.path.join(path, make_filename(e))
|
||||||
if format == 'json':
|
if format == 'json':
|
||||||
content = json.dumps(e.to_dict(), indent=2) + "\n"
|
content = json.dumps(entry_to_dict(e), indent=2) + "\n"
|
||||||
elif format == 'md':
|
elif format in ('md', 'markdown'):
|
||||||
content = e.to_md()
|
content = entry_to_md(e)
|
||||||
elif format == 'txt':
|
elif format in 'xml':
|
||||||
content = u(e)
|
content = entry_to_xml(e)
|
||||||
|
elif format in ('txt', 'text'):
|
||||||
|
content = e.__unicode__()
|
||||||
with codecs.open(full_path, "w", "utf-8") as f:
|
with codecs.open(full_path, "w", "utf-8") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
return "[Journal exported individual files in {0}]".format(path)
|
return u"[Journal exported individual files in {0}]".format(path)
|
||||||
|
|
28
jrnl/util.py
28
jrnl/util.py
|
@ -2,10 +2,8 @@
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
from tzlocal import get_localzone
|
|
||||||
import getpass as gp
|
import getpass as gp
|
||||||
import keyring
|
import keyring
|
||||||
import pytz
|
|
||||||
import json
|
import json
|
||||||
if "win32" in sys.platform:
|
if "win32" in sys.platform:
|
||||||
import colorama
|
import colorama
|
||||||
|
@ -24,12 +22,14 @@ STDOUT = sys.stdout
|
||||||
TEST = False
|
TEST = False
|
||||||
__cached_tz = None
|
__cached_tz = None
|
||||||
|
|
||||||
|
|
||||||
def getpass(prompt="Password: "):
|
def getpass(prompt="Password: "):
|
||||||
if not TEST:
|
if not TEST:
|
||||||
return gp.getpass(prompt)
|
return gp.getpass(prompt)
|
||||||
else:
|
else:
|
||||||
return py23_input(prompt)
|
return py23_input(prompt)
|
||||||
|
|
||||||
|
|
||||||
def get_password(validator, keychain=None, max_attempts=3):
|
def get_password(validator, keychain=None, max_attempts=3):
|
||||||
pwd_from_keychain = keychain and get_keychain(keychain)
|
pwd_from_keychain = keychain and get_keychain(keychain)
|
||||||
password = pwd_from_keychain or getpass()
|
password = pwd_from_keychain or getpass()
|
||||||
|
@ -49,9 +49,11 @@ def get_password(validator, keychain=None, max_attempts=3):
|
||||||
prompt("Extremely wrong password.")
|
prompt("Extremely wrong password.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def get_keychain(journal_name):
|
def get_keychain(journal_name):
|
||||||
return keyring.get_password('jrnl', journal_name)
|
return keyring.get_password('jrnl', journal_name)
|
||||||
|
|
||||||
|
|
||||||
def set_keychain(journal_name, password):
|
def set_keychain(journal_name, password):
|
||||||
if password is None:
|
if password is None:
|
||||||
try:
|
try:
|
||||||
|
@ -61,40 +63,51 @@ def set_keychain(journal_name, password):
|
||||||
elif not TEST:
|
elif not TEST:
|
||||||
keyring.set_password('jrnl', journal_name, password)
|
keyring.set_password('jrnl', journal_name, password)
|
||||||
|
|
||||||
|
|
||||||
def u(s):
|
def u(s):
|
||||||
"""Mock unicode function for python 2 and 3 compatibility."""
|
"""Mock unicode function for python 2 and 3 compatibility."""
|
||||||
return s if PY3 or type(s) is unicode else unicode(s.encode('string-escape'), "unicode_escape")
|
if PY3:
|
||||||
|
return str(s)
|
||||||
|
elif isinstance(s, basestring) and type(s) is not unicode:
|
||||||
|
return unicode(s.encode('string-escape'), "unicode_escape")
|
||||||
|
return unicode(s)
|
||||||
|
|
||||||
|
|
||||||
def py2encode(s):
|
def py2encode(s):
|
||||||
"""Encode in Python 2, but not in python 3."""
|
"""Encode in Python 2, but not in python 3."""
|
||||||
return s.encode("utf-8") if PY2 and type(s) is unicode else s
|
return s.encode("utf-8") if PY2 and type(s) is unicode else s
|
||||||
|
|
||||||
|
|
||||||
def prompt(msg):
|
def prompt(msg):
|
||||||
"""Prints a message to the std err stream defined in util."""
|
"""Prints a message to the std err stream defined in util."""
|
||||||
if not msg.endswith("\n"):
|
if not msg.endswith("\n"):
|
||||||
msg += "\n"
|
msg += "\n"
|
||||||
STDERR.write(u(msg))
|
STDERR.write(u(msg))
|
||||||
|
|
||||||
|
|
||||||
def py23_input(msg=""):
|
def py23_input(msg=""):
|
||||||
STDERR.write(u(msg))
|
STDERR.write(u(msg))
|
||||||
return STDIN.readline().strip()
|
return STDIN.readline().strip()
|
||||||
|
|
||||||
|
|
||||||
def py23_read(msg=""):
|
def py23_read(msg=""):
|
||||||
STDERR.write(u(msg))
|
STDERR.write(u(msg))
|
||||||
return STDIN.read()
|
return STDIN.read()
|
||||||
|
|
||||||
|
|
||||||
def yesno(prompt, default=True):
|
def yesno(prompt, default=True):
|
||||||
prompt = prompt.strip() + (" [Y/n]" if default else " [y/N]")
|
prompt = prompt.strip() + (" [Y/n]" if default else " [y/N]")
|
||||||
raw = py23_input(prompt)
|
raw = py23_input(prompt)
|
||||||
return {'y': True, 'n': False}.get(raw.lower(), default)
|
return {'y': True, 'n': False}.get(raw.lower(), default)
|
||||||
|
|
||||||
|
|
||||||
def load_and_fix_json(json_path):
|
def load_and_fix_json(json_path):
|
||||||
"""Tries to load a json object from a file.
|
"""Tries to load a json object from a file.
|
||||||
If that fails, tries to fix common errors (no or extra , at end of the line).
|
If that fails, tries to fix common errors (no or extra , at end of the line).
|
||||||
"""
|
"""
|
||||||
with open(json_path) as f:
|
with open(json_path) as f:
|
||||||
json_str = f.read()
|
json_str = f.read()
|
||||||
config = fixed = None
|
config = None
|
||||||
try:
|
try:
|
||||||
return json.loads(json_str)
|
return json.loads(json_str)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
@ -113,8 +126,9 @@ def load_and_fix_json(json_path):
|
||||||
prompt("[Entry was NOT added to your journal]")
|
prompt("[Entry was NOT added to your journal]")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def get_text_from_editor(config, template=""):
|
def get_text_from_editor(config, template=""):
|
||||||
tmpfile = os.path.join(tempfile.gettempdir(), "jrnl")
|
tmpfile = os.path.join(tempfile.mktemp(prefix="jrnl"))
|
||||||
with codecs.open(tmpfile, 'w', "utf-8") as f:
|
with codecs.open(tmpfile, 'w', "utf-8") as f:
|
||||||
if template:
|
if template:
|
||||||
f.write(template)
|
f.write(template)
|
||||||
|
@ -126,10 +140,12 @@ def get_text_from_editor(config, template=""):
|
||||||
prompt('[Nothing saved to file]')
|
prompt('[Nothing saved to file]')
|
||||||
return raw
|
return raw
|
||||||
|
|
||||||
|
|
||||||
def colorize(string):
|
def colorize(string):
|
||||||
"""Returns the string wrapped in cyan ANSI escape"""
|
"""Returns the string wrapped in cyan ANSI escape"""
|
||||||
return u"\033[36m{}\033[39m".format(string)
|
return u"\033[36m{}\033[39m".format(string)
|
||||||
|
|
||||||
|
|
||||||
def slugify(string):
|
def slugify(string):
|
||||||
"""Slugifies a string.
|
"""Slugifies a string.
|
||||||
Based on public domain code from https://github.com/zacharyvoase/slugify
|
Based on public domain code from https://github.com/zacharyvoase/slugify
|
||||||
|
@ -141,6 +157,7 @@ def slugify(string):
|
||||||
slug = re.sub(r'[-\s]+', '-', no_punctuation)
|
slug = re.sub(r'[-\s]+', '-', no_punctuation)
|
||||||
return u(slug)
|
return u(slug)
|
||||||
|
|
||||||
|
|
||||||
def int2byte(i):
|
def int2byte(i):
|
||||||
"""Converts an integer to a byte.
|
"""Converts an integer to a byte.
|
||||||
This is equivalent to chr() in Python 2 and bytes((i,)) in Python 3."""
|
This is equivalent to chr() in Python 2 and bytes((i,)) in Python 3."""
|
||||||
|
@ -151,4 +168,3 @@ def byte2int(b):
|
||||||
"""Converts a byte to an integer.
|
"""Converts a byte to an integer.
|
||||||
This is equivalent to ord(bs[0]) on Python 2 and bs[0] on Python 3."""
|
This is equivalent to ord(bs[0]) on Python 2 and bs[0] on Python 3."""
|
||||||
return ord(b)if PY2 else b
|
return ord(b)if PY2 else b
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue