mirror of
https://github.com/ArchiveBox/ArchiveBox
synced 2024-11-10 06:34:16 +00:00
Add submodules and links
This commit is contained in:
parent
326fe69eea
commit
a2694a3e8a
7 changed files with 15 additions and 653 deletions
9
.gitmodules
vendored
9
.gitmodules
vendored
|
@ -14,3 +14,12 @@
|
|||
[submodule "docker"]
|
||||
path = docker
|
||||
url = https://github.com/ArchiveBox/docker-archivebox.git
|
||||
[submodule "archivebox/vendor/base32-crockford"]
|
||||
path = archivebox/vendor/base32-crockford
|
||||
url = https://github.com/jbittel/base32-crockford
|
||||
[submodule "archivebox/vendor/pocket"]
|
||||
path = archivebox/vendor/pocket
|
||||
url = https://github.com/tapanpandita/pocket
|
||||
[submodule "archivebox/vendor/django-taggit"]
|
||||
path = archivebox/vendor/django-taggit
|
||||
url = https://github.com/jazzband/django-taggit
|
||||
|
|
1
archivebox/vendor/base32-crockford
vendored
Submodule
1
archivebox/vendor/base32-crockford
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 1ffb6021485b666ea6a562abd0a1ea6f7021188f
|
172
archivebox/vendor/base32_crockford.py
vendored
172
archivebox/vendor/base32_crockford.py
vendored
|
@ -1,172 +0,0 @@
|
|||
"""
|
||||
base32-crockford
|
||||
================
|
||||
|
||||
A Python module implementing the alternate base32 encoding as described
|
||||
by Douglas Crockford at: http://www.crockford.com/wrmg/base32.html.
|
||||
|
||||
He designed the encoding to:
|
||||
|
||||
* Be human and machine readable
|
||||
* Be compact
|
||||
* Be error resistant
|
||||
* Be pronounceable
|
||||
|
||||
It uses a symbol set of 10 digits and 22 letters, excluding I, L O and
|
||||
U. Decoding is not case sensitive, and 'i' and 'l' are converted to '1'
|
||||
and 'o' is converted to '0'. Encoding uses only upper-case characters.
|
||||
|
||||
Hyphens may be present in symbol strings to improve readability, and
|
||||
are removed when decoding.
|
||||
|
||||
A check symbol can be appended to a symbol string to detect errors
|
||||
within the string.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if not PY3:
|
||||
import string as str
|
||||
|
||||
|
||||
__all__ = ["encode", "decode", "normalize"]
|
||||
|
||||
|
||||
if PY3:
|
||||
string_types = (str,)
|
||||
else:
|
||||
string_types = (basestring,) # noqa
|
||||
|
||||
# The encoded symbol space does not include I, L, O or U
|
||||
symbols = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
|
||||
# These five symbols are exclusively for checksum values
|
||||
check_symbols = '*~$=U'
|
||||
|
||||
encode_symbols = dict((i, ch) for (i, ch) in enumerate(symbols + check_symbols))
|
||||
decode_symbols = dict((ch, i) for (i, ch) in enumerate(symbols + check_symbols))
|
||||
normalize_symbols = str.maketrans('IiLlOo', '111100')
|
||||
valid_symbols = re.compile('^[%s]+[%s]?$' % (symbols,
|
||||
re.escape(check_symbols)))
|
||||
|
||||
base = len(symbols)
|
||||
check_base = len(symbols + check_symbols)
|
||||
|
||||
|
||||
def encode(number, checksum=False, split=0):
|
||||
"""Encode an integer into a symbol string.
|
||||
|
||||
A ValueError is raised on invalid input.
|
||||
|
||||
If checksum is set to True, a check symbol will be
|
||||
calculated and appended to the string.
|
||||
|
||||
If split is specified, the string will be divided into
|
||||
clusters of that size separated by hyphens.
|
||||
|
||||
The encoded string is returned.
|
||||
"""
|
||||
number = int(number)
|
||||
if number < 0:
|
||||
raise ValueError("number '%d' is not a positive integer" % number)
|
||||
|
||||
split = int(split)
|
||||
if split < 0:
|
||||
raise ValueError("split '%d' is not a positive integer" % split)
|
||||
|
||||
check_symbol = ''
|
||||
if checksum:
|
||||
check_symbol = encode_symbols[number % check_base]
|
||||
|
||||
if number == 0:
|
||||
return '0' + check_symbol
|
||||
|
||||
symbol_string = ''
|
||||
while number > 0:
|
||||
remainder = number % base
|
||||
number //= base
|
||||
symbol_string = encode_symbols[remainder] + symbol_string
|
||||
symbol_string = symbol_string + check_symbol
|
||||
|
||||
if split:
|
||||
chunks = []
|
||||
for pos in range(0, len(symbol_string), split):
|
||||
chunks.append(symbol_string[pos:pos + split])
|
||||
symbol_string = '-'.join(chunks)
|
||||
|
||||
return symbol_string
|
||||
|
||||
|
||||
def decode(symbol_string, checksum=False, strict=False):
|
||||
"""Decode an encoded symbol string.
|
||||
|
||||
If checksum is set to True, the string is assumed to have a
|
||||
trailing check symbol which will be validated. If the
|
||||
checksum validation fails, a ValueError is raised.
|
||||
|
||||
If strict is set to True, a ValueError is raised if the
|
||||
normalization step requires changes to the string.
|
||||
|
||||
The decoded string is returned.
|
||||
"""
|
||||
symbol_string = normalize(symbol_string, strict=strict)
|
||||
if checksum:
|
||||
symbol_string, check_symbol = symbol_string[:-1], symbol_string[-1]
|
||||
|
||||
number = 0
|
||||
for symbol in symbol_string:
|
||||
number = number * base + decode_symbols[symbol]
|
||||
|
||||
if checksum:
|
||||
check_value = decode_symbols[check_symbol]
|
||||
modulo = number % check_base
|
||||
if check_value != modulo:
|
||||
raise ValueError("invalid check symbol '%s' for string '%s'" %
|
||||
(check_symbol, symbol_string))
|
||||
|
||||
return number
|
||||
|
||||
|
||||
def normalize(symbol_string, strict=False):
|
||||
"""Normalize an encoded symbol string.
|
||||
|
||||
Normalization provides error correction and prepares the
|
||||
string for decoding. These transformations are applied:
|
||||
|
||||
1. Hyphens are removed
|
||||
2. 'I', 'i', 'L' or 'l' are converted to '1'
|
||||
3. 'O' or 'o' are converted to '0'
|
||||
4. All characters are converted to uppercase
|
||||
|
||||
A TypeError is raised if an invalid string type is provided.
|
||||
|
||||
A ValueError is raised if the normalized string contains
|
||||
invalid characters.
|
||||
|
||||
If the strict parameter is set to True, a ValueError is raised
|
||||
if any of the above transformations are applied.
|
||||
|
||||
The normalized string is returned.
|
||||
"""
|
||||
if isinstance(symbol_string, string_types):
|
||||
if not PY3:
|
||||
try:
|
||||
symbol_string = symbol_string.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
raise ValueError("string should only contain ASCII characters")
|
||||
else:
|
||||
raise TypeError("string is of invalid type %s" %
|
||||
symbol_string.__class__.__name__)
|
||||
|
||||
norm_string = symbol_string.replace('-', '').translate(normalize_symbols).upper()
|
||||
|
||||
if not valid_symbols.match(norm_string):
|
||||
raise ValueError("string '%s' contains invalid characters" % norm_string)
|
||||
|
||||
if strict and norm_string != symbol_string:
|
||||
raise ValueError("string '%s' requires normalization" % symbol_string)
|
||||
|
||||
return norm_string
|
1
archivebox/vendor/base32_crockford.py
vendored
Symbolic link
1
archivebox/vendor/base32_crockford.py
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
base32-crockford/base32_crockford.py
|
1
archivebox/vendor/django-taggit
vendored
Submodule
1
archivebox/vendor/django-taggit
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 1e4dca37e534ca70e99c39fb4198970eb8aad5aa
|
1
archivebox/vendor/pocket
vendored
Submodule
1
archivebox/vendor/pocket
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 3a0c5c76832b0e92923383af3f9831ece7901c2f
|
368
archivebox/vendor/pocket.py
vendored
368
archivebox/vendor/pocket.py
vendored
|
@ -1,368 +0,0 @@
|
|||
# https://github.com/tapanpandita/pocket/blob/master/pocket.py
|
||||
|
||||
import requests
|
||||
import json
|
||||
from functools import wraps
|
||||
|
||||
|
||||
class PocketException(Exception):
|
||||
'''
|
||||
Base class for all pocket exceptions
|
||||
http://getpocket.com/developer/docs/errors
|
||||
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class InvalidQueryException(PocketException):
|
||||
pass
|
||||
|
||||
|
||||
class AuthException(PocketException):
|
||||
pass
|
||||
|
||||
|
||||
class RateLimitException(PocketException):
|
||||
'''
|
||||
http://getpocket.com/developer/docs/rate-limits
|
||||
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class ServerMaintenanceException(PocketException):
|
||||
pass
|
||||
|
||||
EXCEPTIONS = {
|
||||
400: InvalidQueryException,
|
||||
401: AuthException,
|
||||
403: RateLimitException,
|
||||
503: ServerMaintenanceException,
|
||||
}
|
||||
|
||||
|
||||
def method_wrapper(fn):
|
||||
|
||||
@wraps(fn)
|
||||
def wrapped(self, *args, **kwargs):
|
||||
arg_names = list(fn.__code__.co_varnames)
|
||||
arg_names.remove('self')
|
||||
kwargs.update(dict(zip(arg_names, args)))
|
||||
|
||||
url = self.api_endpoints[fn.__name__]
|
||||
payload = dict([
|
||||
(k, v) for k, v in kwargs.items()
|
||||
if v is not None
|
||||
])
|
||||
payload.update(self.get_payload())
|
||||
|
||||
return self.make_request(url, payload)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def bulk_wrapper(fn):
|
||||
|
||||
@wraps(fn)
|
||||
def wrapped(self, *args, **kwargs):
|
||||
arg_names = list(fn.__code__.co_varnames)
|
||||
arg_names.remove('self')
|
||||
kwargs.update(dict(zip(arg_names, args)))
|
||||
|
||||
wait = kwargs.get('wait', True)
|
||||
query = dict(
|
||||
[(k, v) for k, v in kwargs.items() if v is not None]
|
||||
)
|
||||
# TODO: Fix this hack
|
||||
query['action'] = 'add' if fn.__name__ == 'bulk_add' else fn.__name__
|
||||
|
||||
if wait:
|
||||
self.add_bulk_query(query)
|
||||
return self
|
||||
else:
|
||||
url = self.api_endpoints['send']
|
||||
payload = {
|
||||
'actions': [query],
|
||||
}
|
||||
payload.update(self.get_payload())
|
||||
return self.make_request(
|
||||
url,
|
||||
json.dumps(payload),
|
||||
headers={'content-type': 'application/json'},
|
||||
)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
class Pocket(object):
|
||||
'''
|
||||
This class implements a basic python wrapper around the pocket api. For a
|
||||
detailed documentation of the methods and what they do please refer the
|
||||
official pocket api documentation at
|
||||
http://getpocket.com/developer/docs/overview
|
||||
|
||||
'''
|
||||
api_endpoints = dict(
|
||||
(method, 'https://getpocket.com/v3/%s' % method)
|
||||
for method in "add,send,get".split(",")
|
||||
)
|
||||
|
||||
statuses = {
|
||||
200: 'Request was successful',
|
||||
400: 'Invalid request, please make sure you follow the '
|
||||
'documentation for proper syntax',
|
||||
401: 'Problem authenticating the user',
|
||||
403: 'User was authenticated, but access denied due to lack of '
|
||||
'permission or rate limiting',
|
||||
503: 'Pocket\'s sync server is down for scheduled maintenance.',
|
||||
}
|
||||
|
||||
def __init__(self, consumer_key, access_token):
|
||||
self.consumer_key = consumer_key
|
||||
self.access_token = access_token
|
||||
self._bulk_query = []
|
||||
|
||||
self._payload = {
|
||||
'consumer_key': self.consumer_key,
|
||||
'access_token': self.access_token,
|
||||
}
|
||||
|
||||
def get_payload(self):
|
||||
return self._payload
|
||||
|
||||
def add_bulk_query(self, query):
|
||||
self._bulk_query.append(query)
|
||||
|
||||
@staticmethod
|
||||
def _post_request(url, payload, headers):
|
||||
r = requests.post(url, data=payload, headers=headers)
|
||||
return r
|
||||
|
||||
@classmethod
|
||||
def _make_request(cls, url, payload, headers=None):
|
||||
r = cls._post_request(url, payload, headers)
|
||||
|
||||
if r.status_code > 399:
|
||||
error_msg = cls.statuses.get(r.status_code)
|
||||
extra_info = r.headers.get('X-Error')
|
||||
raise EXCEPTIONS.get(r.status_code, PocketException)(
|
||||
'%s. %s' % (error_msg, extra_info)
|
||||
)
|
||||
|
||||
return r.json() or r.text, r.headers
|
||||
|
||||
@classmethod
|
||||
def make_request(cls, url, payload, headers=None):
|
||||
return cls._make_request(url, payload, headers)
|
||||
|
||||
@method_wrapper
|
||||
def add(self, url, title=None, tags=None, tweet_id=None):
|
||||
'''
|
||||
This method allows you to add a page to a user's list.
|
||||
In order to use the /v3/add endpoint, your consumer key must have the
|
||||
"Add" permission.
|
||||
http://getpocket.com/developer/docs/v3/add
|
||||
|
||||
'''
|
||||
|
||||
@method_wrapper
|
||||
def get(
|
||||
self, state=None, favorite=None, tag=None, contentType=None,
|
||||
sort=None, detailType=None, search=None, domain=None, since=None,
|
||||
count=None, offset=None
|
||||
):
|
||||
'''
|
||||
This method allows you to retrieve a user's list. It supports
|
||||
retrieving items changed since a specific time to allow for syncing.
|
||||
http://getpocket.com/developer/docs/v3/retrieve
|
||||
|
||||
'''
|
||||
|
||||
@method_wrapper
|
||||
def send(self, actions):
|
||||
'''
|
||||
This method allows you to make changes to a user's list. It supports
|
||||
adding new pages, marking pages as read, changing titles, or updating
|
||||
tags. Multiple changes to items can be made in one request.
|
||||
http://getpocket.com/developer/docs/v3/modify
|
||||
|
||||
'''
|
||||
|
||||
@bulk_wrapper
|
||||
def bulk_add(
|
||||
self, item_id, ref_id=None, tags=None, time=None, title=None,
|
||||
url=None, wait=True
|
||||
):
|
||||
'''
|
||||
Add a new item to the user's list
|
||||
http://getpocket.com/developer/docs/v3/modify#action_add
|
||||
|
||||
'''
|
||||
|
||||
@bulk_wrapper
|
||||
def archive(self, item_id, time=None, wait=True):
|
||||
'''
|
||||
Move an item to the user's archive
|
||||
http://getpocket.com/developer/docs/v3/modify#action_archive
|
||||
|
||||
'''
|
||||
|
||||
@bulk_wrapper
|
||||
def readd(self, item_id, time=None, wait=True):
|
||||
'''
|
||||
Re-add (unarchive) an item to the user's list
|
||||
http://getpocket.com/developer/docs/v3/modify#action_readd
|
||||
|
||||
'''
|
||||
|
||||
@bulk_wrapper
|
||||
def favorite(self, item_id, time=None, wait=True):
|
||||
'''
|
||||
Mark an item as a favorite
|
||||
http://getpocket.com/developer/docs/v3/modify#action_favorite
|
||||
|
||||
'''
|
||||
|
||||
@bulk_wrapper
|
||||
def unfavorite(self, item_id, time=None, wait=True):
|
||||
'''
|
||||
Remove an item from the user's favorites
|
||||
http://getpocket.com/developer/docs/v3/modify#action_unfavorite
|
||||
|
||||
'''
|
||||
|
||||
@bulk_wrapper
|
||||
def delete(self, item_id, time=None, wait=True):
|
||||
'''
|
||||
Permanently remove an item from the user's account
|
||||
http://getpocket.com/developer/docs/v3/modify#action_delete
|
||||
|
||||
'''
|
||||
|
||||
@bulk_wrapper
|
||||
def tags_add(self, item_id, tags, time=None, wait=True):
|
||||
'''
|
||||
Add one or more tags to an item
|
||||
http://getpocket.com/developer/docs/v3/modify#action_tags_add
|
||||
|
||||
'''
|
||||
|
||||
@bulk_wrapper
|
||||
def tags_remove(self, item_id, tags, time=None, wait=True):
|
||||
'''
|
||||
Remove one or more tags from an item
|
||||
http://getpocket.com/developer/docs/v3/modify#action_tags_remove
|
||||
|
||||
'''
|
||||
|
||||
@bulk_wrapper
|
||||
def tags_replace(self, item_id, tags, time=None, wait=True):
|
||||
'''
|
||||
Replace all of the tags for an item with one or more provided tags
|
||||
http://getpocket.com/developer/docs/v3/modify#action_tags_replace
|
||||
|
||||
'''
|
||||
|
||||
@bulk_wrapper
|
||||
def tags_clear(self, item_id, time=None, wait=True):
|
||||
'''
|
||||
Remove all tags from an item.
|
||||
http://getpocket.com/developer/docs/v3/modify#action_tags_clear
|
||||
|
||||
'''
|
||||
|
||||
@bulk_wrapper
|
||||
def tag_rename(self, item_id, old_tag, new_tag, time=None, wait=True):
|
||||
'''
|
||||
Rename a tag. This affects all items with this tag.
|
||||
http://getpocket.com/developer/docs/v3/modify#action_tag_rename
|
||||
|
||||
'''
|
||||
|
||||
def commit(self):
|
||||
'''
|
||||
This method executes the bulk query, flushes stored queries and
|
||||
returns the response
|
||||
|
||||
'''
|
||||
url = self.api_endpoints['send']
|
||||
payload = {
|
||||
'actions': self._bulk_query,
|
||||
}
|
||||
payload.update(self._payload)
|
||||
self._bulk_query = []
|
||||
|
||||
return self._make_request(
|
||||
url,
|
||||
json.dumps(payload),
|
||||
headers={'content-type': 'application/json'},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_request_token(
|
||||
cls, consumer_key, redirect_uri='http://example.com/', state=None
|
||||
):
|
||||
'''
|
||||
Returns the request token that can be used to fetch the access token
|
||||
|
||||
'''
|
||||
headers = {
|
||||
'X-Accept': 'application/json',
|
||||
}
|
||||
url = 'https://getpocket.com/v3/oauth/request'
|
||||
payload = {
|
||||
'consumer_key': consumer_key,
|
||||
'redirect_uri': redirect_uri,
|
||||
}
|
||||
|
||||
if state:
|
||||
payload['state'] = state
|
||||
|
||||
return cls._make_request(url, payload, headers)[0]['code']
|
||||
|
||||
@classmethod
|
||||
def get_credentials(cls, consumer_key, code):
|
||||
'''
|
||||
Fetches access token from using the request token and consumer key
|
||||
|
||||
'''
|
||||
headers = {
|
||||
'X-Accept': 'application/json',
|
||||
}
|
||||
url = 'https://getpocket.com/v3/oauth/authorize'
|
||||
payload = {
|
||||
'consumer_key': consumer_key,
|
||||
'code': code,
|
||||
}
|
||||
|
||||
return cls._make_request(url, payload, headers)[0]
|
||||
|
||||
@classmethod
|
||||
def get_access_token(cls, consumer_key, code):
|
||||
return cls.get_credentials(consumer_key, code)['access_token']
|
||||
|
||||
@classmethod
|
||||
def get_auth_url(cls, code, redirect_uri='http://example.com'):
|
||||
auth_url = ('https://getpocket.com/auth/authorize'
|
||||
'?request_token=%s&redirect_uri=%s' % (code, redirect_uri))
|
||||
return auth_url
|
||||
|
||||
@classmethod
|
||||
def auth(
|
||||
cls, consumer_key, redirect_uri='http://example.com/', state=None,
|
||||
):
|
||||
'''
|
||||
This is a test method for verifying if oauth worked
|
||||
http://getpocket.com/developer/docs/authentication
|
||||
|
||||
'''
|
||||
code = cls.get_request_token(consumer_key, redirect_uri, state)
|
||||
|
||||
auth_url = 'https://getpocket.com/auth/authorize?request_token='\
|
||||
'%s&redirect_uri=%s' % (code, redirect_uri)
|
||||
raw_input(
|
||||
'Please open %s in your browser to authorize the app and '
|
||||
'press enter:' % auth_url
|
||||
)
|
||||
|
||||
return cls.get_access_token(consumer_key, code)
|
1
archivebox/vendor/pocket.py
vendored
Symbolic link
1
archivebox/vendor/pocket.py
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
pocket/pocket.py
|
113
archivebox/vendor/taggit_utils.py
vendored
113
archivebox/vendor/taggit_utils.py
vendored
|
@ -1,113 +0,0 @@
|
|||
# Taken from https://github.com/jazzband/django-taggit/blob/3b56adb637ab95aca5036c37a358402c825a367c/taggit/utils.py
|
||||
|
||||
def parse_tags(tagstring):
|
||||
"""
|
||||
Parses tag input, with multiple word input being activated and
|
||||
delineated by commas and double quotes. Quotes take precedence, so
|
||||
they may contain commas.
|
||||
|
||||
Returns a sorted list of unique tag names.
|
||||
|
||||
Ported from Jonathan Buchanan's `django-tagging
|
||||
<http://django-tagging.googlecode.com/>`_
|
||||
"""
|
||||
if not tagstring:
|
||||
return []
|
||||
|
||||
# Special case - if there are no commas or double quotes in the
|
||||
# input, we don't *do* a recall... I mean, we know we only need to
|
||||
# split on spaces.
|
||||
if "," not in tagstring and '"' not in tagstring:
|
||||
words = list(set(split_strip(tagstring, " ")))
|
||||
words.sort()
|
||||
return words
|
||||
|
||||
words = []
|
||||
buffer = []
|
||||
# Defer splitting of non-quoted sections until we know if there are
|
||||
# any unquoted commas.
|
||||
to_be_split = []
|
||||
saw_loose_comma = False
|
||||
open_quote = False
|
||||
i = iter(tagstring)
|
||||
try:
|
||||
while True:
|
||||
c = next(i)
|
||||
if c == '"':
|
||||
if buffer:
|
||||
to_be_split.append("".join(buffer))
|
||||
buffer = []
|
||||
# Find the matching quote
|
||||
open_quote = True
|
||||
c = next(i)
|
||||
while c != '"':
|
||||
buffer.append(c)
|
||||
c = next(i)
|
||||
if buffer:
|
||||
word = "".join(buffer).strip()
|
||||
if word:
|
||||
words.append(word)
|
||||
buffer = []
|
||||
open_quote = False
|
||||
else:
|
||||
if not saw_loose_comma and c == ",":
|
||||
saw_loose_comma = True
|
||||
buffer.append(c)
|
||||
except StopIteration:
|
||||
# If we were parsing an open quote which was never closed treat
|
||||
# the buffer as unquoted.
|
||||
if buffer:
|
||||
if open_quote and "," in buffer:
|
||||
saw_loose_comma = True
|
||||
to_be_split.append("".join(buffer))
|
||||
if to_be_split:
|
||||
if saw_loose_comma:
|
||||
delimiter = ","
|
||||
else:
|
||||
delimiter = " "
|
||||
for chunk in to_be_split:
|
||||
words.extend(split_strip(chunk, delimiter))
|
||||
words = list(set(words))
|
||||
words.sort()
|
||||
return words
|
||||
|
||||
|
||||
def split_strip(string, delimiter=","):
|
||||
"""
|
||||
Splits ``string`` on ``delimiter``, stripping each resulting string
|
||||
and returning a list of non-empty strings.
|
||||
|
||||
Ported from Jonathan Buchanan's `django-tagging
|
||||
<http://django-tagging.googlecode.com/>`_
|
||||
"""
|
||||
if not string:
|
||||
return []
|
||||
|
||||
words = [w.strip() for w in string.split(delimiter)]
|
||||
return [w for w in words if w]
|
||||
|
||||
|
||||
def edit_string_for_tags(tags):
|
||||
"""
|
||||
Given list of ``Tag`` instances, creates a string representation of
|
||||
the list suitable for editing by the user, such that submitting the
|
||||
given string representation back without changing it will give the
|
||||
same list of tags.
|
||||
|
||||
Tag names which contain commas will be double quoted.
|
||||
|
||||
If any tag name which isn't being quoted contains whitespace, the
|
||||
resulting string of tag names will be comma-delimited, otherwise
|
||||
it will be space-delimited.
|
||||
|
||||
Ported from Jonathan Buchanan's `django-tagging
|
||||
<http://django-tagging.googlecode.com/>`_
|
||||
"""
|
||||
names = []
|
||||
for tag in tags:
|
||||
name = tag.name
|
||||
if "," in name or " " in name:
|
||||
names.append('"%s"' % name)
|
||||
else:
|
||||
names.append(name)
|
||||
return ", ".join(sorted(names))
|
1
archivebox/vendor/taggit_utils.py
vendored
Symbolic link
1
archivebox/vendor/taggit_utils.py
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
django-taggit/taggit/utils.py
|
Loading…
Reference in a new issue