Skip to content

Commit

Permalink
upload of initial version of Python script and first README version
Browse files Browse the repository at this point in the history
  • Loading branch information
tumpotumpta authored and tumpotumpta committed Mar 1, 2021
1 parent 7d313d8 commit 4e9cf04
Show file tree
Hide file tree
Showing 2 changed files with 318 additions and 1 deletion.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
# generate-stickers-AveryL7871
# generate-stickers-AveryL7871

This script reads sample names from a txt file (one name per line) and arranges them on a sheet of A4 paper so that they can be printed on Avery-Zweckform L7871 labels. The current version of the script is written in Python and is conceptually based on v1, which was written in bash.

## Requirements
- A UNIX type console (e.g. Terminal on macOS)
- Python 3.5 or higher (e.g. Python 3.9.1 from https://www.python.org/downloads/mac-osx/ or Python 3.8.5 as it comes with the latest Anaconda release https://www.anaconda.com/products/individual)
- LaTeX (e.g. TeX Live/MacTeX for macOS or any other TeX distribution, https://tug.org/mactex/)

## How to use
1. Put the script (generateStickers.py) in the folder that you want the output pdf to be in.
2. Navigate to this folder using the command line: cd path/to/your/folder
3. Run the script with: python3 generateStickers.py
4. Follow the instructions that appear in the console.
304 changes: 304 additions & 0 deletions generateStickers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
""" Version 2.0 dated 24th Feb 2021 by Gleb Ebert
This script reads sample names from a list, presents the user with
a couple of options to modify the names and arranges them on a
sheet of A4 paper so that it can be printed on Avery-Zweckform
L7871 labels
"""

from pathlib import Path # we use this for file path operations
import subprocess # this is for passing commands to unix shell

# create class with objects to format console output
class color:
PURPLE = "\033[95m"
CYAN = "\033[96m"
DARKCYAN = "\033[36m"
BLUE = "\033[94m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
RED = "\033[91m"
BOLD = "\033[1m"
UNDERLINE = "\033[4m"
END = "\033[0m"

#######################################################################
# This section deals with the initial input of sample names.
#######################################################################

# keep asking for file names until file exists or user aborts script
while True:
# get input file name from user and store its name in variable
input_file = input(f"{color.BOLD + color.DARKCYAN}"
"Enter the name of the txt file containing your strain/isolate names "
f"(one per line) followed by [ENTER] to confirm: {color.END}")

# check if user input includes ".txt" suffix and add it if absent
if not input_file.endswith(".txt"):
input_file += ".txt"

# if file does not exist, print error message and exit script
if not Path(input_file).exists():
print(f"\n{color.BOLD + color.RED}File {input_file} not found. "
"Make sure file is present in your working directory:\n"
f"{Path().cwd()}\n"
"To change your working directory type "
f"'cd /Path/to/your/directory' then hit [ENTER]{color.END}")

# ask user whether he wants to type the file name again
input_file_again = input(f"{color.BOLD + color.DARKCYAN}"
"Do you want to type the file name again? "
f"Type \"yes\" or \"no\": \n{color.END}")

# if user wants to try again, restart loop
# otherwise exit script
if input_file_again.casefold() == "yes".casefold():
continue
else:
quit()
else:
break

# open file in read mode
with open(input_file, "r") as file_handle:
# convert file contents into a list
names_list_original = file_handle.read().splitlines()

names_number = len(names_list_original) # get number of names

# print some of the sample names
print(f"\n{color.BOLD + color.DARKCYAN}"
f"Your file contains {names_number} names:\n{names_list_original[0]}, "
f"{names_list_original[1]} ... {names_list_original[-1]}{color.END}")

# ask user whether he wants to type the file name again
input_file_ok = input(f"{color.BOLD + color.DARKCYAN}Do you want to continue "
f"with these names? Type \"yes\" or \"no\": {color.END}")

# exit script if user says no, otherwise continue
if input_file_ok.casefold() == "no".casefold():
quit()

# query user on output file name
output_file_name = input(f"\n{color.BOLD + color.DARKCYAN}"
f"\nType the name of your output file without suffix: {color.END}")

# check if user input includes ".txt" suffix and add it if not
if not output_file_name.endswith(".txt"):
output_file_name += ".txt"

# remove old output file and ignore error if it does not exist
try:
output_file_path = Path(str(Path().absolute()) + "/" + output_file_name)
output_file_path.unlink()
except (FileNotFoundError):
pass

#######################################################################
# The following section deals with name suffixes
#######################################################################

# give user choice whether to add suffixes
input_suffix_if = input(f"\n{color.BOLD + color.DARKCYAN}"
"\nDo you want to add suffixes to your sample names? "
f"Type \"yes\" or \"no\": {color.END}")

if input_suffix_if.casefold() == "yes".casefold():
# print explanation of inner workings once before continuing
print(f"\n{color.BOLD + color.DARKCYAN}"
"\n==========================================")
print("\nIn the following part of the script you will supply groups of "
"suffixes (e.g. treatment names or replicate numbers) separated by "
"spaces: \"CTRL TREAT1 TREAT2 TREAT3\". Each suffix will be combined "
"with each sample name (e.g. Strain1-TREAT1, Strain1-TREAT2 ... "
"Strain10-TREAT3). You will also have to opportunity to supply "
"multiple suffix groups one after the other (the result of this would "
"be something like Strain1-TREAT1-Replicate1, "
"Strain1-TREAT1-Replicate2 ...).{color.END}")

# initiate list with names to be modified
names_list_old = names_list_original

# Keep asking for suffixes and adding them to sample names
# until user stops loop.
while True:
# ask for group of suffixes
input_suffix_group = input(f"{color.BOLD + color.DARKCYAN}"
f"\nEnter a group of suffixes: {color.END}")

# split input into list of words
input_suffixes = input_suffix_group.split()

# reinitiate list so it is empty
names_list_new = []

# loop through all names and add each suffix
for name in names_list_old:
for suffix in input_suffixes:
names_list_new.append(name + "-" + suffix)

# replace old with new list
names_list_old = names_list_new

# ask user whether to run through another interation of the
# suffix loop
input_suffix_continue = input(f"\n{color.BOLD + color.DARKCYAN}"
"\nDo you want to add another group of suffixes? "
f"Type \"yes\" or \"no\": {color.END}")

# if user answers anything other than yes break out of loop,
# otherwise repeat
if input_suffix_continue.casefold() != "yes".casefold():
break
else:
continue

# Create output file in append mode, loop through latest list with
# modified names and write each one to a new line in the file.
with open(output_file_path, "a+") as sampleFile:
for item in names_list_new:
sampleFile.write(f"{item}\n")
else:
names_list_new = names_list_original

#######################################################################
# logic and parameters for LaTeX typesetting
#######################################################################

# give user choice whether to print month and year
input_date_if = input(f"\n{color.BOLD + color.DARKCYAN}"
"\nDo you want to print the current month and year on the stickers? "
f"Type \"yes\" or \"no\": {color.END}")

# give user choice whether to print month and year
input_date_format = input(f"\n{color.BOLD + color.DARKCYAN}"
"\nDo you want the date to include the day in addition to month and year?"
f" Type \"yes\" or \"no\": {color.END}")

if input_date_format.casefold() == "yes".casefold():
latex_date = "\t\t\\DTMtwodigits{##3}-\\DTMtwodigits{##2}-##1"
else:
latex_date = "\t\t\\DTMtwodigits{##2}-##1"

# function that returns sticker content
def return_sticker(x):
sticker = names_list_new[x].replace("_", "\_")
if len(sticker) >= 16:
sticker = "{\\ssmall " + sticker + "}"
if input_date_if.casefold() == "yes".casefold():
sticker = sticker + " \\mbox{\\DATE}"
else:
sticker = sticker + "\\newline"
return sticker

# set output file to .tex
latex_file_path = output_file_path.with_suffix(".tex")

# remove old output file and ignore error if file does not exist
try:
latex_file_path.unlink()
except (FileNotFoundError):
pass

# Round number of pages needed to fit all stickers up to nearest
# whole page.
names_number_new = len(names_list_new)
latex_pages = (names_number_new // 189) + 1

#######################################################################
# typeset LaTeX file
#######################################################################

# save latex document preamble in variable
latex_preamble = \
"""\\documentclass[a4paper]{article} % document definition
% used to adjust page margins to 3.5mm
\\usepackage[top=1.2cm, bottom=1.2cm, left=0.5cm, right=0.5cm]{geometry}
% used for table with columns of a defined width
\\usepackage{tabularx}
% used to add white space after column rows
\\usepackage{booktabs}
% to format date
\\usepackage{datetime2}
% for more font sizes
\\usepackage{moresize}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\\pagestyle{empty} % no page numbers
\\setlength{\\parindent}{0pt} % set paragraph indent to zero
% define new date style
\\DTMnewdatestyle{mydate}{%
\\renewcommand{\\DTMdisplaydate}[4]{%
"""\
f"{latex_date}"\
"""
}%
\\renewcommand{\\DTMDisplaydate}{\\DTMdisplaydate}%
}
\\DTMsetdatestyle{mydate} % set new datestyle as default
\\newcommand{\\DATE}{\\today} % create alias for current date command
% defines custom column type for table
\\newcolumntype{Y}{>{\\centering\\arraybackslash}X}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\\begin{document}
\\batchmode % disable command line output
% scriptsize/ssmall, bold and sans-serif font throughout the entire document
\\scriptsize \\bfseries \\sffamily
% table definition: spans the entire page in (text)width
% @{} removes white space before first and after last row
% *{7}{Y} defines seven columns of equal width with text centering
% sample name and date rows alternate with different amounts of spacing\n\n"""

# create .tex file and write to it
with open(latex_file_path, "a+") as latex_file:
latex_file.write(latex_preamble) # write preamble once

# variable to track the current position in the list of names
n = 0

# loop through pages of final sticker layout
for page_number in range(latex_pages):
# start each page with the opening of the table environment
latex_file.write(f"% Page {page_number+1}\n"
"\\begin{tabularx}{\linewidth}{@{}*{7}{Y}@{}}\n")

# loop through each line of and write sticker contents to it
for line_number in range(27):
# add tab character at beginning of line
latex_file.write("\t")
#
for l in range (7):
# if names to be printed still left, do so
if (n < (names_number_new-1) and l < 6):
latex_file.write(f"{return_sticker(n)} & ")
elif (n == (names_number_new-1) or l == 6):
latex_file.write(f"{return_sticker(n)}")
else:
break
n += 1
# end line after 7 stickers
latex_file.write(" \\\\")
# if line is the last one of the page
if (line_number == 26):
latex_file.write(" \\addlinespace[0.05cm]\n")
# if next line is not the last line of the page
else:
latex_file.write(" \\addlinespace[0.470878cm]\n")
# if all names were printed, break loop
if (n == names_number_new):
break
# close table environment at the end of the page
latex_file.write("\\end{tabularx}\n\n")
# reenable command line output and end document
latex_file.write("\\scrollmode\n\\end{document}")

# call pdflatex to typeset .tex file # text = FALSE
subprocess.run(["pdflatex", latex_file_path], stdout=subprocess.DEVNULL)
# open resulting pdf file
subprocess.run(["open", latex_file_path.with_suffix(".pdf")])

0 comments on commit 4e9cf04

Please sign in to comment.