Skip to content

Instantly share code, notes, and snippets.

@bwiernik
Created Oct 14, 2018
Embed
What would you like to do?
#! /usr/bin/env python3
#
# zotxform
# BW custom Zotero locale transform.
#
# AUTHORS: Ian-Mathew Hornburg ‹imhornburg@gmail.com›
# Optima Language Analytics LLC
# LICENSE: [TBD]
#
# Copyright (c) 2014–2015, the authors.
# All rights reserved.
# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
from shutil import copytree, rmtree
from sys import exit
import argparse
import json
import logging
import os
import os.path as path
import zipfile as z
CONFIG_PATH = 'zotxform.json'
TEMPDIR_PATH = 'zotxform_temp'
FIREFOX_MANIFEST_PATH = 'chrome.manifest'
FIREFOX_MANIFEST_LOCALE = '\n\nlocale zotero bw-US chrome/locale/bw-US/zotero/'
STANDALONE_WIN_DFLT_ARCHIVE_PATH = "C:\\Program Files (x86)\\Zotero Standalone\\zotero.jar"
STANDALONE_WIN_DFLT_MANIFEST_PATH = "C:\\Program Files (x86)\\Zotero Standalone\\chrome.manifest"
STANDALONE_MANIFEST_LOCALE = '\n\nlocale zotero bw-US jar:zotero.jar!/chrome/locale/bw-US/zotero/'
replacement_table = {
'itemTypes.patent = Patent': 'itemTypes.patent = Class',
'itemFields.series = Series': 'itemFields.series = Series/Type',
'itemFields.assignee = Assignee': 'itemFields.assignee = Level',
'itemFields.patentNumber = Patent Number': 'itemFields.patentNumber = Lecture Number',
'itemFields.priorityNumbers = Priority Numbers': 'itemFields.priorityNumbers = Course Title',
'itemFields.issueDate = Issue Date': 'itemFields.issueDate = Class Date',
'itemFields.references = References': 'itemFields.references = Primary Instructor',
'itemFields.legalStatus = Legal Status': 'itemFields.legalStatus = Program',
'itemFields.country = Country': 'itemFields.country = Department',
'itemFields.applicationNumber = Application Number': 'itemFields.applicationNumber = Course Number',
'itemFields.proceedingsTitle = Proceedings Title': 'itemFields.proceedingsTitle = Session/Proceedings',
'itemFields.issuingAuthority = Issuing Authority': 'itemFields.issuingAuthority = Material Type',
'itemFields.filingDate = Filing Date': 'itemFields.filingDate = Term',
'creatorTypes.editor = Editor': 'creatorTypes.editor = Editor/Chair',
'creatorTypes.inventor = Inventor': 'creatorTypes.inventor = Instructor',
'creatorTypes.attorneyAgent = Attorney/Agent': 'creatorTypes.attorneyAgent = Notes By',
}
def transform(args):
def check_file(fn):
fn_exists = path.exists(fn) and path.isfile(fn)
if not fn_exists:
raise OSError("The specified file does not exist or cannot be accessed.")
sys.exit(1)
if args['firefox']:
archive_location = {}
if args['file']:
# Ensure that we have a full absolute path on the passed filename
fn_archive = path.abspath(args['file'])
check_file(fn_archive)
# Overwrite any existing configuration
with open(CONFIG_PATH, encoding='utf_8', mode='w') as config_file:
archive_location['firefox'] = fn_archive
json.dump(archive_location, config_file)
else:
try:
# Check for an existing config file
with open(CONFIG_PATH, encoding='utf_8', mode='r') as config_file:
archive_location = json.load(config_file)
except:
raise FileNotFoundError("Could not find zotxform.json. Please specify a Zotero Firefox archive to be patched.")
if args['firefox']:
fn_archive = path.abspath(archive_location.get('firefox'))
fn_manifest = FIREFOX_MANIFEST_PATH
if args['standalone']:
# If we're Standalone, just use the hardcoded defaults
fn_archive = path.abspath(STANDALONE_WIN_DFLT_ARCHIVE_PATH)
fn_manifest = path.abspath(STANDALONE_WIN_DFLT_MANIFEST_PATH)
try:
with z.ZipFile(fn_archive, mode='r') as archive:
os.mkdir(TEMPDIR_PATH)
os.chdir(TEMPDIR_PATH)
# We wanna preserve the exact order of the files in the archive,
# since for some applications, order matters (e.g., needing to have
# the chrome.manifest first). So everything that’s going into the
# archive needs to be explicitly enumerated in this list, starting
# with the existing contents.
file_list = archive.namelist()
if args['firefox']:
if FIREFOX_MANIFEST_PATH in archive.namelist():
logger.info("Manifest found.")
else:
raise FileNotFoundError("chrome.manifest file not found.")
if args['standalone']:
if os.path.isfile(fn_manifest):
logger.info("Manifest found.")
else:
raise FileNotFoundError("chrome.manifest file not found.")
# Extract and end scope, closing archive
archive.extractall()
# Append custom locale line to manifest
with open(fn_manifest, encoding='utf_8', mode='r') as manifest:
s = manifest.read()
if args['firefox']:
if FIREFOX_MANIFEST_LOCALE in s:
raise ValueError("Manifest already patched.")
else:
s = s.join([s, FIREFOX_MANIFEST_LOCALE])
if args['standalone']:
if STANDALONE_MANIFEST_LOCALE in s:
raise ValueError("Manifest already patched.")
s = s.join([s, STANDALONE_MANIFEST_LOCALE])
logger.info("Writing patched manifest.")
with open(fn_manifest, encoding='utf_8', mode='w') as manifest:
manifest.write(s)
# Copy the contents of en-US locale to our new custom locale directory
new_locale_dir = path.join('chrome', 'locale', 'bw-US')
copytree(path.join('chrome', 'locale', 'en-US'), new_locale_dir)
file_list.append(new_locale_dir)
# [TO-DO] Make this cleaner, more clear; better comprehensions
# Walk the directory tree, grabbing all nodes along the way, and
# append this list to our archive list.
for e in [path.join(dirpath, f) for dirpath, dirname, filename in os.walk(new_locale_dir) for f in filename]:
file_list.append(e)
# Read in properties file, patch, write
with open(path.join(new_locale_dir, 'zotero', 'zotero.properties'), encoding='utf_8', mode='r') as f:
properties = f.read()
for target in replacement_table.keys():
properties = properties.replace(target, replacement_table.get(target))
with open(path.join(new_locale_dir, 'zotero', 'zotero.properties'), encoding='utf_8', mode='w') as f:
f.write(properties)
# Truncate and write new archive
with z.ZipFile(fn_archive, compression=z.ZIP_DEFLATED, mode='w') as archive:
for e in file_list:
archive.write(e)
if 'bw-US' in e or e == FIREFOX_MANIFEST_PATH:
print(e)
logger.info("Done.")
finally:
os.chdir(os.pardir)
rmtree(TEMPDIR_PATH)
# Command-line argument parsing - --- --- --- --- --- --- --- --- --- --- --- #
parser = argparse.ArgumentParser(
add_help=True,
description="Custom Zotero locale transform.",
epilog="Copyright (c) 2014–2015, Optima Language Analytics LLC. All rights reserved."
)
parser.add_argument('-v', '--verbose', action='store_true', help='increase verbosity')
parser.add_argument('-f', '--firefox', action='store_true', help='patch Zotero Firefox')
parser.add_argument('-s', '--standalone', action='store_true', help='patch Zotero Standalone')
parser.add_argument('file', nargs='?', default=False, help="A valid Zotero Firefox settings file to be patched. If none given, zotxform will attempt to use the last valid archive location.")
# “main()” -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
if __name__ == '__main__':
logging.basicConfig(format="[zotxform] {levelname} :: {message}", style='{')
logger = logging.getLogger('zotxform')
args = vars(parser.parse_args())
if args['verbose']:
logger.setLevel('INFO')
del args['verbose']
logger.info("zotxform v0.612")
# If no action flag is given, warn and bail.
if not any(args.values()):
logger.warn("At least one argument is required.")
exit(1)
if args['firefox'] and args['standalone']:
logger.warn("Specify only one type of Zotero settings file to patch.")
exit(1)
if args['file'] and args['standalone']:
logger.warn("Cannot specify file. Zotero Standalone patch uses default files located at " + STANDALONE_WIN_DFLT_ARCHIVE_PATH + " and " + STANDALONE_WIN_DFLT_MANIFEST_PATH + ".")
exit(1)
transform(args)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment