-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
upload of initial version of Python script and first README version
- Loading branch information
tumpotumpta
authored and
tumpotumpta
committed
Mar 1, 2021
1 parent
7d313d8
commit 4e9cf04
Showing
2 changed files
with
318 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")]) |