From aa374797f769c0230c0ca3917d3040cff46eb4b6 Mon Sep 17 00:00:00 2001 From: Dean Jackson Date: Sun, 6 Jan 2019 23:44:10 +0100 Subject: [PATCH] Read directories from Zotero config files. Closes #21 --- README.md | 22 ++--- ...fredworkflow => ZotHero-1.2.alfredworkflow | Bin 517541 -> 518613 bytes src/info.plist | 88 ++++++++--------- src/lib/zothero/config.py | 90 ++++++++++++++++++ src/lib/zothero/core.py | 21 ++-- src/lib/zothero/csl.py | 5 + src/lib/zothero/formatting.py | 3 + src/lib/zothero/util.py | 10 +- 8 files changed, 170 insertions(+), 69 deletions(-) rename ZotHero-1.1.alfredworkflow => ZotHero-1.2.alfredworkflow (93%) create mode 100644 src/lib/zothero/config.py diff --git a/README.md b/README.md index 69f0db6..b73bd8d 100644 --- a/README.md +++ b/README.md @@ -82,17 +82,17 @@ When you copy a citation, ZotHero puts both HTML and rich text (RTF) representat Configuration ------------- -The workflow partly manages its own configuration with the keyword `zotconf`, but you may need to use the [workflow configuration sheet][conf-sheet] if you don't use Zotero 5's default data directory. +The workflow reads Zotero's own config files and partly manages its own configuration with the keyword `zotconf`, but you may need to use the [workflow configuration sheet][conf-sheet] if the workflow can't read Zotero's config files. ### Zotero data ### -The workflow uses your Zotero database and styles, therefore it needs to know where to find them. By default, the workflow looks in `~/Zotero` (the default location for Zotero 5). +The workflow uses your Zotero database and styles, therefore it needs to know where to find them. The workflow tries to read Zotero's own configuration files, and falls back to `~/Zotero` (the default location for Zotero 5). -If you data are stored somewhere else, you need to set `ZOTERO_DIR` in the [workflow configuration sheet][conf-sheet]. +If the workflow can't find your data, you need to set `ZOTERO_DIR` in the [workflow configuration sheet][conf-sheet]. -If you have set a "Linked Attachment Base Directory" in Zotero, enter its path for `ATTACHMENTS_DIR` in the [configuration sheet][conf-sheet]. +Similarly, if you have set a "Linked Attachment Base Directory" in Zotero, but the workflow can't find the directory, enter its path for `ATTACHMENTS_DIR` in the [configuration sheet][conf-sheet]. **Note**: You can use the UNIX shortcut `~` to represent your home directory, e.g. `~/Zotero` for Zotero 5's default directory. @@ -178,13 +178,13 @@ Theses are all settings available in the [workflow configuration sheet][conf-she You probably shouldn't edit the `CITE_STYLE` or `LOCALE` variables yourself, as there's no guarantee the value you set is actually available. Adjust them using the `zotconf` keyword. -| Variable | Meaning | -|-------------------|------------------------------------------------------| -| `ATTACHMENTS_DIR` | Path to your Zotero attachments. | -| `CITE_STYLE` | Citation style copied by `⌘↩` and `⌥↩` | -| `LOCALE` | Locale for citations. Default: `en-US` (US English). | -| `ZOTERO_DIR` | Path to your Zotero data. | -| | | +| Variable | Meaning | +|-------------------|------------------------------------------------------------------------| +| `ATTACHMENTS_DIR` | Path to your Zotero attachments. Read from Zotero's config by default. | +| `CITE_STYLE` | Citation style copied by `⌘↩` and `⌥↩` | +| `LOCALE` | Locale for citations. Default: `en-US` (US English). | +| `ZOTERO_DIR` | Path to your Zotero data. Read from Zotero's config by default. | + Licence & thanks diff --git a/ZotHero-1.1.alfredworkflow b/ZotHero-1.2.alfredworkflow similarity index 93% rename from ZotHero-1.1.alfredworkflow rename to ZotHero-1.2.alfredworkflow index a607277919585d6df9bc401165ad4f5ac74736ce..44f1f7d04552cb41f69fc328d6895d3c9a27afed 100644 GIT binary patch delta 17010 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 config - alfredfiltersresults - - alfredfiltersresultsmatchmode - 0 - argumenttrimmode - 0 - argumenttype - 1 - escaping - 102 - queuedelaycustom - 3 - queuedelayimmediatelyinitially - - queuedelaymode - 0 - queuemode - 1 - runningsubtext - - script - ./zh citations "$id" "$1" - - scriptargtype - 1 - scriptfile - - subtext - - title - - type - 5 - withspace - + triggerid + copy-citation type - alfred.workflow.input.scriptfilter + alfred.workflow.trigger.external uid - 8956B5DB-42F0-4570-9265-ECD302239D56 + AEE6452E-9186-4609-8AD4-D74F469F1A79 version - 2 + 1 config @@ -1106,15 +1073,48 @@ test -n "$autopaste" && flags+=(--paste) config - triggerid - copy-citation + alfredfiltersresults + + alfredfiltersresultsmatchmode + 0 + argumenttrimmode + 0 + argumenttype + 1 + escaping + 102 + queuedelaycustom + 3 + queuedelayimmediatelyinitially + + queuedelaymode + 0 + queuemode + 1 + runningsubtext + + script + ./zh citations "$id" "$1" + + scriptargtype + 1 + scriptfile + + subtext + + title + + type + 5 + withspace + type - alfred.workflow.trigger.external + alfred.workflow.input.scriptfilter uid - AEE6452E-9186-4609-8AD4-D74F469F1A79 + 8956B5DB-42F0-4570-9265-ECD302239D56 version - 1 + 2 config @@ -2224,7 +2224,7 @@ Edit the `ZOTERO_DIR` variable to point to Zotero 5's data directory if you aren version - 1.1 + 1.2 webaddress https://github.com/deanishe/zothero diff --git a/src/lib/zothero/config.py b/src/lib/zothero/config.py new file mode 100644 index 0000000..c482b8d --- /dev/null +++ b/src/lib/zothero/config.py @@ -0,0 +1,90 @@ +# encoding: utf-8 +# +# Copyright (c) 2019 Dean Jackson +# +# MIT Licence. See http://opensource.org/licenses/MIT +# +# Created on 2019-01-06 +# + +"""Read Zotero configuration files.""" + +from ConfigParser import SafeConfigParser +import logging +import os +import re + +from .util import unicodify + +CONFDIR = os.path.expanduser(u'~/Library/Application Support/Zotero') +PROFILES = os.path.join(CONFDIR, u'profiles.ini') +DATADIR_KEY = 'extensions.zotero.dataDir' +ATTACH_KEY = 'extensions.zotero.baseAttachmentPath' +# Start of preference lines +PREFIX = 'user_pref("' + +log = logging.getLogger(__name__) +log.addHandler(logging.NullHandler()) + + +def read(): + """Load data and attachments directories from Zotero prefs.""" + p = find_prefs() + if p: + return parse_prefs(p) + + return None, None + + +def find_prefs(): + """Find prefs.js by parsing profiles.ini.""" + conf = SafeConfigParser() + try: + conf.read(PROFILES) + except Exception as err: + log.error('reading profiles.ini: %s', err) + return None + + for section in conf.sections(): + if conf.has_option(section, 'Name') and \ + conf.get(section, 'Name') == 'default': + path = conf.get(section, 'Path') + if conf.getboolean(section, 'IsRelative'): + path = os.path.join(CONFDIR, path) + + return unicodify(os.path.join(path, 'prefs.js')) + + return None + + +def parse_prefs(path): + """Extract relevant preferences from prefs.js.""" + datadir = attachdir = None + + def extract_value(s): + m = re.search(r'"(.+)"', s) + if not m: + return None + + return unicodify(m.group(1)) + + with open(path) as fp: + for line in fp: + line = line.strip() + if not line.startswith(PREFIX): + continue + + line = line[len(PREFIX):] + i = line.find('",') + if i < 0: + continue + + key = line[:i] + if key == DATADIR_KEY: + datadir = extract_value(line[i + 2:]) + log.debug('[config] datadir=%r', datadir) + elif key == ATTACH_KEY: + attachdir = extract_value(line[i + 2:]) + log.debug('[config] attachdir=%r', attachdir) + + return datadir, attachdir diff --git a/src/lib/zothero/core.py b/src/lib/zothero/core.py index 80ad642..062dc65 100644 --- a/src/lib/zothero/core.py +++ b/src/lib/zothero/core.py @@ -14,6 +14,7 @@ import logging import os +from .config import read as read_config from .util import copyifnewer, unicodify, shortpath # Default location of Zotero's data in version 5 @@ -57,11 +58,14 @@ def __init__(self, cachedir, zot_data_dir=None, zot_attachments_dir=None): # it's necessary to make a copy. self._copy_path = os.path.join(cachedir, 'zotero.sqlite') - # Attributes to back lazy-loading properties - self._zotero_dir = zot_data_dir # Zotero's data directory - self._attachments_dir = zot_attachments_dir # Zotero's attachment base + # Read Zotero config files + datadir, attachdir = read_config() + + # Zotero's data directory + self._zotero_dir = zot_data_dir or datadir + # Zotero's attachment base + self._attachments_dir = zot_attachments_dir or attachdir self._zot = None # Zotero object - # self._cache = None # Cache object self._index = None # Index object self._styles = None # Styles object @@ -161,15 +165,6 @@ def update_index(self, force=False): """Update the search index.""" self.index.update(self.zotero, force) - # @property - # def cache(self): - # """Top-level cache.""" - # if not self._cache: - # from .cache import Cache - # self._cache = Cache(os.path.join(self.cachedir, 'cache.sqlite')) - - # return self._cache - @property def styles(self): """CSL Styles loader. diff --git a/src/lib/zothero/csl.py b/src/lib/zothero/csl.py index 50d797a..8d9ba6c 100644 --- a/src/lib/zothero/csl.py +++ b/src/lib/zothero/csl.py @@ -38,6 +38,7 @@ def get_field(zfield, ztype): Returns: unicode: CSL field name or ``None``. + """ # Get "canonical" Zotero field name zfield = REMAP.get(zfield, zfield) @@ -56,6 +57,7 @@ def get_creator(ztype): Returns: unicode: CSL creator type or ``None``. + """ # Get "canonical" Zotero type ztype = REMAP.get(ztype, ztype) @@ -73,6 +75,7 @@ def get_type(ztype): Returns: unicode: CSL type or ``None``. + """ # Get "canonical" Zotero type ztype = REMAP.get(ztype, ztype) @@ -106,6 +109,7 @@ def entry_data(e): Returns: dict: CSL data. + """ data = {'id': e.key} ctype = get_type(e.type) @@ -144,6 +148,7 @@ def parse_date(datestr): Returns: dict: ``date-parts`` dict for CSL JSON. + """ parsed = util.parse_date(datestr) if parsed: diff --git a/src/lib/zothero/formatting.py b/src/lib/zothero/formatting.py index 8c812c6..1f3688b 100644 --- a/src/lib/zothero/formatting.py +++ b/src/lib/zothero/formatting.py @@ -33,6 +33,7 @@ def title(self): Returns: unicode: Formatted title. + """ title = self.e.title @@ -53,6 +54,7 @@ def creators(self): Returns: unicode: Formatted list of creators. + """ n = len(self.e.creators) if n == 0: @@ -96,6 +98,7 @@ def year(self): Returns: unicode: Formatted year. + """ if not self.e.year: return 'xxx.' diff --git a/src/lib/zothero/util.py b/src/lib/zothero/util.py index b97e7ff..d910c95 100644 --- a/src/lib/zothero/util.py +++ b/src/lib/zothero/util.py @@ -19,7 +19,6 @@ from os.path import getmtime import re from shutil import copyfile -import string import time from unicodedata import normalize @@ -42,6 +41,7 @@ def dt2sqlite(dt): Returns: str: Sqlite-formatted datetime string. + """ return dt.strftime(SQLITE_DATE_FMT) @@ -57,6 +57,7 @@ def sqlite2dt(s): Returns: datetime: `datetime` equivalent of `s`. + """ s = s.split('.')[0] return datetime.strptime(s, SQLITE_DATE_FMT) @@ -82,6 +83,7 @@ def strip(cls, html): Returns: unicode: Text content of HTML. + """ p = cls() p.feed(html) @@ -117,6 +119,7 @@ def strip_tags(html): Returns: unicode: Text contained in HTML. + """ return HTMLText.strip(html) @@ -133,6 +136,7 @@ def copyifnewer(source, copy): Returns: str: Path to copy + """ if not os.path.exists(copy) or getmtime(source) > getmtime(copy): log.debug('[util] copying %r to %r ...', @@ -154,6 +158,7 @@ def unicodify(s, encoding='utf-8'): Returns: unicode: Decoded Unicode string. + """ if isinstance(s, unicode): return s @@ -183,6 +188,7 @@ def asciify(s): Returns: unicode: String containing only ASCII characters. + """ u = normalize('NFD', unicodify(s)) s = u.encode('us-ascii', 'ignore') @@ -201,6 +207,7 @@ def parse_date(datestr): Returns: unicode: Parsed date if ``datestr``. + """ if not datestr: return None @@ -228,6 +235,7 @@ def json_serialise(obj): Raises: TypeError: Raised if ``obj`` is not a `datetime.date` + """ if isinstance(obj, date): return obj.isoformat()