pdfformfill/install.py
2018-11-02 18:48:08 +01:00

330 lines
12 KiB
Python
Executable file

#! /usr/bin/python3
# -*- coding: UTF-8 -*-
"""Make the fillpdf program available on this computer."""
import os
import os.path
import argparse
import shutil
import sys
import subprocess # for cloning
from constants import (CONFIGFILE, CONFIG_INSTALLDIR,
CONFIG_FORMDIR, CONFIG_SEPARATOR)
FORMS_DIRNAME = "TU-Dokumente"
FORMS_URL = ("https://gitlab.math.tu-dresden.de/s1140568/" + FORMS_DIRNAME +
".git")
SCRIPTS_TO_INSTALL = ["fillform", "reise", "uninstallfillpdf"]
def getPath():
"""Get list of directories that are in the PATH environment variable.
Returns:
List of directories as strings.
If loading path variable did not work, this list is empty.
"""
return os.environ.get('PATH', "").split(':') # "" is the default value
def checkLinkAccess(dir):
"""Tell if I can create a link there.
Try to create a link. If there was no error and indeed there
is a link, remove the link and return True.
Otherwise return False.
"""
# find a filename that is not used yet:
linkloc = os.path.join(dir, "z")
while os.path.lexists(linkloc):
linkloc = linkloc + "z"
if len(linkloc) > len(dir) + 100:
print("Found files with names with 1 to 100 z's in " +
"directory " + dir +
" Probably error.")
return False
try:
# try to create a link to the arbitrary position .
os.symlink(os.curdir, linkloc)
except FileNotFoundError as fnfe:
# the directory dir does not exist
return False
except PermissionError as pe:
# a cannot create a link there
return False
else:
try:
os.remove(linkloc)
except FileNotFoundError as fnfe:
print("In order to check if I have write access to the " +
"directory " + dir + " I created a link " + linkloc +
" there." +
" I cannot remove it so do it yourself.")
except PermissionError as pe:
print("In order to check if I have write access to the " +
"directory " + dir + " I created a link " + linkloc +
" there." +
" I cannot remove it so do it yourself.")
return True
def defaultPath():
"""Choose a suitable default directory for installing.
Try to access the path variable.
If possible use first entry where I can create a symlink and is of the form:
/usr/local/bin
~/bin
~/.config/bin
If not possible use first where I can create a link.
"""
pathdirs = getPath()
# check if sudo/ root:
rootdir = os.path.join("/", "usr", "local", "bin")
if rootdir in pathdirs and checkLinkAccess(rootdir):
return rootdir
for userdir in [os.path.join(os.environ.get("HOME", ""), ".local", "bin"),
os.path.join(os.environ.get("HOME", ""), "bin")]:
if userdir in pathdirs:
if checkLinkAccess(userdir):
return userdir
try:
os.mkdirs(userdir)
except OSError:
# already exists
# some other error, do not use this
# print("Debug: userdir link creation failed")
pass
else:
if checkLinkAccess(userdir):
return userdir
else:
# something did not work
print("Debug: userdir link creation check failed " +
"after creating dir")
else:
print("Debug: " + userdir + " is not in the path. It should be" +
" on a typical system.")
# print("Debug: non of the typical directories worked. " +
# "Use the first possible.")
for directory in pathdirs:
if checkLinkAccess(directory):
return directory
print("""No suitable path for installing was found. Do one of the following:
• execute this script as sudo/ root
• add a directory where you have write access in the path
• specify a directory via the --path option and add this directory"""
+ " to the path.")
raise FileNotFoundError("No directory for installation found.")
def createProgramSymlinks(where):
"""Create symlinks to some of scripts in this project.
Arguments:
where: the directory where to create the symlinks.
Raises:
multiple possibilities of error
"""
here = os.path.dirname(__file__)
origloc = 0
linkloc = 1
# dict { script : (where it is, where the link should be) }
# print("Debug: here: ", here)
scripts = {script : (os.path.abspath(
os.path.join(here, script + ".py")),
os.path.join(where, script))
for script in SCRIPTS_TO_INSTALL}
# print("Debug: scripts: ", scripts)
todoscripts = []
# check if the scripts exist, i.e. I am at the right place
for script in scripts:
# F_OK = does file exist?
if not os.access(scripts[script][origloc], os.F_OK):
raise FileNotFoundError("I cannot find the script " + script +
" in " + scripts[script][origloc] + ".")
if os.access(scripts[script][linkloc], os.F_OK, follow_symlinks=False):
if os.path.islink(scripts[script][linkloc]):
if not os.path.exists(scripts[script][linkloc]):
# os.path.exists says False on broken symlinks
raise FileExistsError("The symlink " +
scripts[script][linkloc] +
" already exists." +
" It is a broken symlink." +
" You may like to delete it " +
"and start " +
" this install script again.")
elif os.path.samefile(os.readlink(scripts[script][linkloc]),
scripts[script][origloc]):
# print("Debug: link " + script +
# " already links correctly.")
# nothing to do: link already links correctly
pass
else:
raise FileExistsError("The symlink "
+ scripts[script][linkloc] +
" already exists and points " +
"somewhere else.")
else:
raise FileExistsError("The file "
+ scripts[script][linkloc] +
" already exists.")
else:
todoscripts.append(script)
for script in todoscripts:
try:
os.symlink(scripts[script][origloc], scripts[script][linkloc])
except NotImplementedError:
print("The python doc says this happens only on Windows" +
" previous to Windows 6.0 (Vista). You should really " +
"know better than using Windows.")
raise
except OSError as e:
print("Apparently you do not have the permissions to create" +
" a symlink in the directory " + where +
". Call this install script with --path <dir>" +
" with a <dir> where you have the permissions and" +
" that is in the path.")
raise
def writeConfig(installdir, formdir):
"""Write the config file.
Try to write to the file $HOME/.config/.fillpdfrc.
arguments:
installdir: where the programs are installed
formdir: where the form files are
write one line per info, no line if info is None
"""
try:
with open(CONFIGFILE, "a+") as configfile:
# a = append
# if you use only the last entries in the configfile this is OK
# and you do not overwrite what might be interesting to
# the user
# + = create if not exist
if installdir is not None:
print(CONFIG_INSTALLDIR + CONFIG_SEPARATOR +
os.path.abspath(installdir), file=configfile)
else:
print("Debug: No installdir in config saved.")
if formdir is not None:
print(CONFIG_FORMDIR + CONFIG_SEPARATOR +
os.path.abspath(formdir), file=configfile)
else:
print("Debug: No directory for the form files " +
"in config saved.")
except:
print("Debug: Error at writing configfile:")
raise
else:
pass
# print("Debug: config file written")
def parse():
"""Create the argument parser (including the help) and parse the arguments.
Options/ Arguments:
--path directory
--formdir directory
"""
parser = argparse.ArgumentParser(
description=("Install fillpdf on this computer " +
"and show help how to start using it."))
# formatter_class=argparse.RawDescriptionHelpFormatter,
# epilog=("To start go to the new directory and run " +
# "'./fillform Antrag.pdf'"))
parser.add_argument("--path", default=None,
help=("The directory where the scripts should " +
"be installed. Should be in the path. " +
"If not specified, you will be asked " +
"where to install."))
parser.add_argument("--formdir", default=None,
help=("The directory where the config (form) files " +
"for the pdf forms are. " +
"If not specified, look next to the " +
"parent directory of this script " +
"and if it is not there pull from gitlab."))
return vars(parser.parse_args())
def defaultConfig():
"""Check if ../TU-Dokumente exists.
If not, try to clone TU-Dokumente here and return this new directory.
"""
here = os.path.dirname(__file__)
nextTo = os.path.join(here, "..", FORMS_DIRNAME)
if os.path.exists(nextTo):
return nextTo
nextTo = os.path.join(here, FORMS_DIRNAME)
if os.path.exists(nextTo):
# already in current dir
return nextTo
try:
clone = subprocess.Popen([
"git", "clone", FORMS_URL],
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
try:
errorcode = clone.wait(timeout=30)
except TimeoutExpired:
print("Cloning the form files from " +
FORMS_URL + " took too long.")
else:
if errorcode != 0:
print("Debug: cloning the form files from " +
FORMS_URL + " did not work with errorcode " +
str(errorcode) + ".")
else:
# return newly cloned and created directory
return os.path.normpath(os.path.join(here,
FORMS_DIRNAME))
except Exception as e:
print("Trying to clone the form file repo failed in the" +
" following way:", e)
# no sensible default found
return None
def checkpdftk():
"""Check if pdftk is installed.
Without pdftk the software is useless so tell the user to install
it if necessary.
"""
if not shutil.which("pdftk"):
print("This software suite requires pdftk. Install it, such that" +
" the correct binary of the name pdftk is available in the "+
"PATH. Otherwise you get cryptic error messages.")
if __name__ == "__main__":
args = parse()
installdir = args["path"]
if installdir is None:
installdir = defaultPath()
formdir = args["formdir"]
if formdir is None:
formdir = defaultConfig()
# getting None again is OK
print("Chose directory " + installdir + " for installing. ",
"(Installing is just creating symlinks to the scripts.)")
createProgramSymlinks(installdir)
writeConfig(installdir, formdir)
checkpdftk()
print("Everything successfully installed. You should be able to " +
"start fillform, reise and uninstall-fillpdf now.")