initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
/.quarto/
|
||||
**/*.quarto_ipynb
|
||||
@@ -0,0 +1,65 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
PROJECT_NAME: Manuscript
|
||||
PYTHON: '{{if eq OS "windows"}}python{{else}}python3{{end}}'
|
||||
UTILS: "../resources/scripts/task_utils.py"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
desc: "List available tasks"
|
||||
cmd: task --list
|
||||
|
||||
render:
|
||||
desc: "Render with the default (non-anonymized) profile, all formats"
|
||||
cmd: quarto render --profile=default --output-dir _output/default
|
||||
|
||||
render:anonymized:
|
||||
desc: "Render with the anonymized profile, all formats"
|
||||
cmd: quarto render --profile=anonymized --output-dir _output/anonymized
|
||||
|
||||
render:html:
|
||||
desc: "Render to HTML only (default profile)"
|
||||
cmd: quarto render --profile=default --to html --output-dir _output/default
|
||||
|
||||
render:docx:
|
||||
desc: "Render to docx only (default profile)"
|
||||
cmd: quarto render --profile=default --to docx --output-dir _output/default
|
||||
|
||||
render:jats:
|
||||
desc: "Render to JATS XML only (default profile)"
|
||||
cmd: quarto render --profile=default --to jats --output-dir _output/default
|
||||
|
||||
render:pdf:
|
||||
desc: "Render to PDF only (default profile) - requires pdf format configured in _quarto.yml"
|
||||
cmd: quarto render --profile=default --to pdf --output-dir _output/default
|
||||
|
||||
package:
|
||||
desc: "Zip the default-profile rendered output into Manuscript-default.zip"
|
||||
deps: [render]
|
||||
cmd: "{{.PYTHON}} {{.UTILS}} zip _output/default {{.PROJECT_NAME}}-default.zip"
|
||||
sources:
|
||||
- _output/default/**/*
|
||||
generates:
|
||||
- "{{.PROJECT_NAME}}-default.zip"
|
||||
|
||||
package:anonymized:
|
||||
desc: "Zip the anonymized-profile rendered output into Manuscript-anonymized.zip"
|
||||
deps: [render:anonymized]
|
||||
cmd: "{{.PYTHON}} {{.UTILS}} zip _output/anonymized {{.PROJECT_NAME}}-anonymized.zip"
|
||||
sources:
|
||||
- _output/anonymized/**/*
|
||||
generates:
|
||||
- "{{.PROJECT_NAME}}-anonymized.zip"
|
||||
|
||||
package:data:
|
||||
desc: "Zip the data/ folder into data.zip (skips with a warning if data/ doesn't exist)"
|
||||
cmd: "{{.PYTHON}} {{.UTILS}} zip-if-exists data data.zip"
|
||||
sources:
|
||||
- data/**/*
|
||||
generates:
|
||||
- data.zip
|
||||
|
||||
clean:
|
||||
desc: "Remove _output, .quarto, and stray *_files/*_cache folders"
|
||||
cmd: "{{.PYTHON}} {{.UTILS}} clean-project ."
|
||||
@@ -0,0 +1,287 @@
|
||||
% Options for packages loaded elsewhere
|
||||
% Options for packages loaded elsewhere
|
||||
\PassOptionsToPackage{unicode}{hyperref}
|
||||
\PassOptionsToPackage{hyphens}{url}
|
||||
\PassOptionsToPackage{dvipsnames,svgnames,x11names}{xcolor}
|
||||
%
|
||||
\documentclass[
|
||||
american,
|
||||
letterpaper,
|
||||
DIV=11,
|
||||
numbers=noendperiod]{scrartcl}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{amsmath,amssymb}
|
||||
\setcounter{secnumdepth}{-\maxdimen} % remove section numbering
|
||||
\usepackage{iftex}
|
||||
\ifPDFTeX
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{textcomp} % provide euro and other symbols
|
||||
\else % if luatex or xetex
|
||||
\usepackage{unicode-math} % this also loads fontspec
|
||||
\defaultfontfeatures{Scale=MatchLowercase}
|
||||
\defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1}
|
||||
\fi
|
||||
\usepackage{lmodern}
|
||||
\ifPDFTeX\else
|
||||
% xetex/luatex font selection
|
||||
\fi
|
||||
% Use upquote if available, for straight quotes in verbatim environments
|
||||
\IfFileExists{upquote.sty}{\usepackage{upquote}}{}
|
||||
\IfFileExists{microtype.sty}{% use microtype if available
|
||||
\usepackage[]{microtype}
|
||||
\UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts
|
||||
}{}
|
||||
\makeatletter
|
||||
\@ifundefined{KOMAClassName}{% if non-KOMA class
|
||||
\IfFileExists{parskip.sty}{%
|
||||
\usepackage{parskip}
|
||||
}{% else
|
||||
\setlength{\parindent}{0pt}
|
||||
\setlength{\parskip}{6pt plus 2pt minus 1pt}}
|
||||
}{% if KOMA class
|
||||
\KOMAoptions{parskip=half}}
|
||||
\makeatother
|
||||
% Make \paragraph and \subparagraph free-standing
|
||||
\makeatletter
|
||||
\ifx\paragraph\undefined\else
|
||||
\let\oldparagraph\paragraph
|
||||
\renewcommand{\paragraph}{
|
||||
\@ifstar
|
||||
\xxxParagraphStar
|
||||
\xxxParagraphNoStar
|
||||
}
|
||||
\newcommand{\xxxParagraphStar}[1]{\oldparagraph*{#1}\mbox{}}
|
||||
\newcommand{\xxxParagraphNoStar}[1]{\oldparagraph{#1}\mbox{}}
|
||||
\fi
|
||||
\ifx\subparagraph\undefined\else
|
||||
\let\oldsubparagraph\subparagraph
|
||||
\renewcommand{\subparagraph}{
|
||||
\@ifstar
|
||||
\xxxSubParagraphStar
|
||||
\xxxSubParagraphNoStar
|
||||
}
|
||||
\newcommand{\xxxSubParagraphStar}[1]{\oldsubparagraph*{#1}\mbox{}}
|
||||
\newcommand{\xxxSubParagraphNoStar}[1]{\oldsubparagraph{#1}\mbox{}}
|
||||
\fi
|
||||
\makeatother
|
||||
|
||||
|
||||
\usepackage{longtable,booktabs,array}
|
||||
\usepackage{calc} % for calculating minipage widths
|
||||
% Correct order of tables after \paragraph or \subparagraph
|
||||
\usepackage{etoolbox}
|
||||
\makeatletter
|
||||
\patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{}
|
||||
\makeatother
|
||||
% Allow footnotes in longtable head/foot
|
||||
\IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}}
|
||||
\makesavenoteenv{longtable}
|
||||
\usepackage{graphicx}
|
||||
\makeatletter
|
||||
\newsavebox\pandoc@box
|
||||
\newcommand*\pandocbounded[1]{% scales image to fit in text height/width
|
||||
\sbox\pandoc@box{#1}%
|
||||
\Gscale@div\@tempa{\textheight}{\dimexpr\ht\pandoc@box+\dp\pandoc@box\relax}%
|
||||
\Gscale@div\@tempb{\linewidth}{\wd\pandoc@box}%
|
||||
\ifdim\@tempb\p@<\@tempa\p@\let\@tempa\@tempb\fi% select the smaller of both
|
||||
\ifdim\@tempa\p@<\p@\scalebox{\@tempa}{\usebox\pandoc@box}%
|
||||
\else\usebox{\pandoc@box}%
|
||||
\fi%
|
||||
}
|
||||
% Set default figure placement to htbp
|
||||
\def\fps@figure{htbp}
|
||||
\makeatother
|
||||
|
||||
|
||||
|
||||
\ifLuaTeX
|
||||
\usepackage[bidi=basic]{babel}
|
||||
\else
|
||||
\usepackage[bidi=default]{babel}
|
||||
\fi
|
||||
% get rid of language-specific shorthands (see #6817):
|
||||
\let\LanguageShortHands\languageshorthands
|
||||
\def\languageshorthands#1{}
|
||||
\ifLuaTeX
|
||||
\usepackage[english]{selnolig} % disable illegal ligatures
|
||||
\fi
|
||||
|
||||
|
||||
\setlength{\emergencystretch}{3em} % prevent overfull lines
|
||||
|
||||
\providecommand{\tightlist}{%
|
||||
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
\KOMAoption{captions}{tableheading}
|
||||
\usepackage{pdfpages}
|
||||
\pagenumbering{gobble}
|
||||
\usepackage{booktabs}
|
||||
\usepackage{caption}
|
||||
\DeclareCaptionLabelSeparator{period-newline}{.\par}
|
||||
\usepackage{setspace}
|
||||
\usepackage{float} % enables [H]
|
||||
\usepackage[section]{placeins} % <- add [section]
|
||||
\makeatletter
|
||||
\@ifpackageloaded{caption}{}{\usepackage{caption}}
|
||||
\AtBeginDocument{%
|
||||
\ifdefined\contentsname
|
||||
\renewcommand*\contentsname{Table of contents}
|
||||
\else
|
||||
\newcommand\contentsname{Table of contents}
|
||||
\fi
|
||||
\ifdefined\listfigurename
|
||||
\renewcommand*\listfigurename{List of Figures}
|
||||
\else
|
||||
\newcommand\listfigurename{List of Figures}
|
||||
\fi
|
||||
\ifdefined\listtablename
|
||||
\renewcommand*\listtablename{List of Tables}
|
||||
\else
|
||||
\newcommand\listtablename{List of Tables}
|
||||
\fi
|
||||
\ifdefined\figurename
|
||||
\renewcommand*\figurename{Figure}
|
||||
\else
|
||||
\newcommand\figurename{Figure}
|
||||
\fi
|
||||
\ifdefined\tablename
|
||||
\renewcommand*\tablename{Table}
|
||||
\else
|
||||
\newcommand\tablename{Table}
|
||||
\fi
|
||||
}
|
||||
\@ifpackageloaded{float}{}{\usepackage{float}}
|
||||
\floatstyle{ruled}
|
||||
\@ifundefined{c@chapter}{\newfloat{codelisting}{h}{lop}}{\newfloat{codelisting}{h}{lop}[chapter]}
|
||||
\floatname{codelisting}{Listing}
|
||||
\newcommand*\listoflistings{\listof{codelisting}{List of Listings}}
|
||||
\makeatother
|
||||
\makeatletter
|
||||
\makeatother
|
||||
\makeatletter
|
||||
\@ifpackageloaded{caption}{}{\usepackage{caption}}
|
||||
\@ifpackageloaded{subcaption}{}{\usepackage{subcaption}}
|
||||
\makeatother
|
||||
\usepackage{bookmark}
|
||||
\IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available
|
||||
\urlstyle{same}
|
||||
\hypersetup{
|
||||
pdftitle={Title},
|
||||
pdfauthor={, , and },
|
||||
pdflang={en-US},
|
||||
pdfkeywords={Open Science Practices, Open Access, Open Data, Open
|
||||
Materials, Preregistration, Text Mining, Machine Learning
|
||||
Classification, AI-assisted Coding, Replication},
|
||||
colorlinks=true,
|
||||
linkcolor={blue},
|
||||
filecolor={Maroon},
|
||||
citecolor={Blue},
|
||||
urlcolor={Blue},
|
||||
pdfcreator={LaTeX via pandoc}}
|
||||
|
||||
|
||||
\title{Title}
|
||||
\author{John Doe\textsuperscript{$\dagger{}$,1,*} \and John
|
||||
Roe\textsuperscript{1} \and Jane Roe\textsuperscript{$\dagger{}$,1,2}}
|
||||
\date{}
|
||||
\begin{document}
|
||||
\maketitle
|
||||
\begin{abstract}
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
|
||||
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
|
||||
voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
|
||||
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
|
||||
amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
|
||||
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
|
||||
rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem
|
||||
ipsum dolor sit amet.
|
||||
\end{abstract}
|
||||
|
||||
|
||||
\textsuperscript{$\dagger{}$}
|
||||
These authors contributed equally to this work.
|
||||
|
||||
\textsuperscript{1} John Doe Center for Technology, John Doe University,
|
||||
Doetown, Germany.\\
|
||||
\textsuperscript{2} Institute of Technology, John Doe University,
|
||||
Doetown, Germany.
|
||||
|
||||
\textsuperscript{*} Correspondence: \href{mailto:john.doe@jdct.edu}{John
|
||||
Doe \textless{}john.doe@jdct.edu\textgreater{}}
|
||||
|
||||
% keeps the title page numbers empty
|
||||
\pagestyle{empty}
|
||||
\newpage
|
||||
\pagenumbering{arabic}
|
||||
\pagestyle{headings}
|
||||
\onehalfspacing % 1.5 line spacing from here on
|
||||
|
||||
\section{Introduction}\label{introduction}
|
||||
|
||||
\section{Background}\label{background}
|
||||
|
||||
\section{Research Design}\label{research-design}
|
||||
|
||||
\subsection{Data}\label{data}
|
||||
|
||||
\subsection{Method}\label{method}
|
||||
|
||||
\section{Results}\label{results}
|
||||
|
||||
\section{Discussion}\label{discussion}
|
||||
|
||||
\section{Conclusion}\label{conclusion}
|
||||
|
||||
Conclusions here.
|
||||
|
||||
\section*{Acknowlegments}\label{acknowlegments}
|
||||
\addcontentsline{toc}{section}{Acknowlegments}
|
||||
|
||||
I would like to thank my advisor, {[}Advisor Name{]}, for his guidance,
|
||||
constructive feedback, and steady support throughout this project. His
|
||||
expertise and encouragement were invaluable in shaping both the research
|
||||
and this publication.
|
||||
|
||||
\section*{Data availability}\label{data-availability}
|
||||
\addcontentsline{toc}{section}{Data availability}
|
||||
|
||||
Materials, Data and Code are made available at a public OSF-repository
|
||||
that can be accessed here:
|
||||
|
||||
\begin{itemize}
|
||||
\tightlist
|
||||
\item
|
||||
https://osf.io/
|
||||
\end{itemize}
|
||||
|
||||
\section*{Funding}\label{funding}
|
||||
\addcontentsline{toc}{section}{Funding}
|
||||
|
||||
This research received no external funding.
|
||||
|
||||
\section*{Conflict of interest}\label{conflict-of-interest}
|
||||
\addcontentsline{toc}{section}{Conflict of interest}
|
||||
|
||||
The authors declare no conflict of interest.
|
||||
|
||||
\section*{Author Biography}\label{author-biography}
|
||||
\addcontentsline{toc}{section}{Author Biography}
|
||||
|
||||
Jane doe is a scientist.
|
||||
|
||||
\section*{Bibliography}\label{bibliography}
|
||||
\addcontentsline{toc}{section}{Bibliography}
|
||||
|
||||
\phantomsection\label{refs}
|
||||
|
||||
|
||||
|
||||
|
||||
\end{document}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,312 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving
|
||||
and Interchange DTD v1.2 20190208//EN" "JATS-archivearticle1.dtd">
|
||||
|
||||
<article xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink" dtd-version="1.2" article-type="other">
|
||||
|
||||
<front>
|
||||
|
||||
|
||||
<article-meta>
|
||||
|
||||
|
||||
<title-group>
|
||||
<article-title>Title</article-title>
|
||||
</title-group>
|
||||
|
||||
<contrib-group>
|
||||
<contrib contrib-type="author" equal-contrib="yes" corresp="yes">
|
||||
<contrib-id contrib-id-type="orcid">0000-1111-2222-3333</contrib-id>
|
||||
<name>
|
||||
<surname>Doe</surname>
|
||||
<given-names>John</given-names>
|
||||
</name>
|
||||
<string-name>John Doe</string-name>
|
||||
|
||||
<email>john.doe@jdct.edu</email>
|
||||
<xref ref-type="aff" rid="jdct">a</xref>
|
||||
<xref ref-type="corresp" rid="cor-1">*</xref>
|
||||
<xref ref-type="deceased" rid="equal-1">‡</xref>
|
||||
</contrib>
|
||||
<contrib contrib-type="author">
|
||||
<contrib-id contrib-id-type="orcid">0000-3333-2222-1111</contrib-id>
|
||||
<name>
|
||||
<surname>Roe</surname>
|
||||
<given-names>John</given-names>
|
||||
</name>
|
||||
<string-name>John Roe</string-name>
|
||||
|
||||
<xref ref-type="aff" rid="jdct">a</xref>
|
||||
</contrib>
|
||||
<contrib contrib-type="author" equal-contrib="yes">
|
||||
<contrib-id contrib-id-type="orcid">0000-2222-1111-3333</contrib-id>
|
||||
<name>
|
||||
<surname>Roe</surname>
|
||||
<given-names>Jane</given-names>
|
||||
</name>
|
||||
<string-name>Jane Roe</string-name>
|
||||
|
||||
<xref ref-type="aff" rid="jdct">a</xref>
|
||||
<xref ref-type="aff" rid="iot">b</xref>
|
||||
<xref ref-type="deceased" rid="equal-3">‡</xref>
|
||||
</contrib>
|
||||
</contrib-group>
|
||||
<author-notes>
|
||||
<corresp id="cor-DoeJohnJohn Doe">john.doe@jdct.edu</corresp>
|
||||
<fn id="equal-DoeJohnJohn Doe" fn-type="equal" symbol="‡"><p>John
|
||||
Doe</p></fn>
|
||||
<fn id="equal-RoeJaneJane Roe" fn-type="equal" symbol="‡"><p>Jane
|
||||
Roe</p></fn>
|
||||
</author-notes>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<history></history>
|
||||
|
||||
|
||||
<abstract>
|
||||
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
|
||||
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
|
||||
rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem
|
||||
ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing
|
||||
elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna
|
||||
aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo
|
||||
dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus
|
||||
est Lorem ipsum dolor sit amet.</p>
|
||||
</abstract>
|
||||
<kwd-group kwd-group-type="author">
|
||||
<kwd>Open Science Practices</kwd>
|
||||
<kwd>Open Access</kwd>
|
||||
<kwd>Open Data</kwd>
|
||||
<kwd>Open Materials</kwd>
|
||||
<kwd>Preregistration</kwd>
|
||||
<kwd>Text Mining</kwd>
|
||||
<kwd>Machine Learning Classification</kwd>
|
||||
<kwd>AI-assisted Coding</kwd>
|
||||
<kwd>Replication</kwd>
|
||||
</kwd-group>
|
||||
|
||||
|
||||
|
||||
|
||||
</article-meta>
|
||||
|
||||
</front>
|
||||
|
||||
<body>
|
||||
<p><sup>*</sup> These authors contributed equally to this work.</p>
|
||||
<p><sup>1</sup> John Doe Center for Technology, John Doe University,
|
||||
Doetown, Germany.
|
||||
<sup>2</sup> Institute of Technology, John Doe University, Doetown,
|
||||
Germany.</p>
|
||||
<p><sup>✉</sup> Correspondence:
|
||||
<ext-link ext-link-type="uri" xlink:href="mailto:john.doe@jdct.edu">John
|
||||
Doe <john.doe@jdct.edu></ext-link></p>
|
||||
<sec id="introduction">
|
||||
<title>Introduction</title>
|
||||
</sec>
|
||||
<sec id="background">
|
||||
<title>Background</title>
|
||||
</sec>
|
||||
<sec id="research-design">
|
||||
<title>Research Design</title>
|
||||
<sec id="data">
|
||||
<title>Data</title>
|
||||
</sec>
|
||||
<sec id="method">
|
||||
<title>Method</title>
|
||||
</sec>
|
||||
</sec>
|
||||
<sec id="results">
|
||||
<title>Results</title>
|
||||
</sec>
|
||||
<sec id="discussion">
|
||||
<title>Discussion</title>
|
||||
</sec>
|
||||
<sec id="conclusion">
|
||||
<title>Conclusion</title>
|
||||
<p>Conclusions here.</p>
|
||||
</sec>
|
||||
<sec id="acknowlegments">
|
||||
<title>Acknowlegments</title>
|
||||
<p>I would like to thank my advisor, [Advisor Name], for his guidance,
|
||||
constructive feedback, and steady support throughout this project. His
|
||||
expertise and encouragement were invaluable in shaping both the
|
||||
research and this publication.</p>
|
||||
</sec>
|
||||
<sec id="data-availability">
|
||||
<title>Data availability</title>
|
||||
<p>Materials, Data and Code are made available at a public
|
||||
OSF-repository that can be accessed here:</p>
|
||||
<list list-type="bullet">
|
||||
<list-item>
|
||||
<p>https://osf.io/</p>
|
||||
</list-item>
|
||||
</list>
|
||||
</sec>
|
||||
<sec id="funding">
|
||||
<title>Funding</title>
|
||||
<p>This research received no external funding.</p>
|
||||
</sec>
|
||||
<sec id="conflict-of-interest">
|
||||
<title>Conflict of interest</title>
|
||||
<p>The authors declare no conflict of interest.</p>
|
||||
</sec>
|
||||
<sec id="author-biography">
|
||||
<title>Author Biography</title>
|
||||
<p>Jane doe is a scientist.</p>
|
||||
</sec>
|
||||
<sec id="bibliography">
|
||||
<title>Bibliography</title>
|
||||
</sec>
|
||||
</body>
|
||||
|
||||
<back>
|
||||
</back>
|
||||
|
||||
<sub-article article-type="notebook" id="nb-1-nb-article">
|
||||
<front-stub>
|
||||
<title-group>
|
||||
<article-title>Title</article-title>
|
||||
</title-group>
|
||||
<contrib-group>
|
||||
<contrib contrib-type="author" equal-contrib="yes" corresp="yes">
|
||||
<contrib-id contrib-id-type="orcid">0000-1111-2222-3333</contrib-id>
|
||||
<name>
|
||||
<surname>Doe</surname>
|
||||
<given-names>John</given-names>
|
||||
</name>
|
||||
<string-name>John Doe</string-name>
|
||||
|
||||
<email>john.doe@jdct.edu</email>
|
||||
<xref ref-type="aff" rid="jdct-nb-article">a</xref>
|
||||
<xref ref-type="corresp" rid="cor-1-nb-article">*</xref>
|
||||
<xref ref-type="deceased" rid="equal-1-nb-article">‡</xref>
|
||||
</contrib>
|
||||
<contrib contrib-type="author">
|
||||
<contrib-id contrib-id-type="orcid">0000-3333-2222-1111</contrib-id>
|
||||
<name>
|
||||
<surname>Roe</surname>
|
||||
<given-names>John</given-names>
|
||||
</name>
|
||||
<string-name>John Roe</string-name>
|
||||
|
||||
<xref ref-type="aff" rid="jdct-nb-article">a</xref>
|
||||
</contrib>
|
||||
<contrib contrib-type="author" equal-contrib="yes">
|
||||
<contrib-id contrib-id-type="orcid">0000-2222-1111-3333</contrib-id>
|
||||
<name>
|
||||
<surname>Roe</surname>
|
||||
<given-names>Jane</given-names>
|
||||
</name>
|
||||
<string-name>Jane Roe</string-name>
|
||||
|
||||
<xref ref-type="aff" rid="jdct-nb-article">a</xref>
|
||||
<xref ref-type="aff" rid="iot-nb-article">b</xref>
|
||||
<xref ref-type="deceased" rid="equal-3-nb-article">‡</xref>
|
||||
</contrib>
|
||||
</contrib-group>
|
||||
<author-notes>
|
||||
<corresp id="cor-DoeJohnJohn Doe-nb-article">john.doe@jdct.edu</corresp>
|
||||
<fn id="equal-DoeJohnJohn Doe-nb-article" fn-type="equal" symbol="‡"><p>John
|
||||
Doe</p></fn>
|
||||
<fn id="equal-RoeJaneJane Roe-nb-article" fn-type="equal" symbol="‡"><p>Jane
|
||||
Roe</p></fn>
|
||||
</author-notes>
|
||||
<abstract>
|
||||
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
|
||||
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
|
||||
sed diam voluptua. At vero eos et accusam et justo duo dolores et ea
|
||||
rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem
|
||||
ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing
|
||||
elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna
|
||||
aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo
|
||||
dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus
|
||||
est Lorem ipsum dolor sit amet.</p>
|
||||
</abstract>
|
||||
</front-stub>
|
||||
|
||||
<body>
|
||||
<p><sup>*</sup> These authors contributed equally to this work.</p>
|
||||
<p><sup>1</sup> John Doe Center for Technology, John Doe University,
|
||||
Doetown, Germany.
|
||||
<sup>2</sup> Institute of Technology, John Doe University, Doetown,
|
||||
Germany.</p>
|
||||
<p><sup>✉</sup> Correspondence:
|
||||
<ext-link ext-link-type="uri" xlink:href="mailto:john.doe@jdct.edu">John
|
||||
Doe <john.doe@jdct.edu></ext-link></p>
|
||||
<sec id="introduction-nb-article">
|
||||
<title>Introduction</title>
|
||||
</sec>
|
||||
<sec id="background-nb-article">
|
||||
<title>Background</title>
|
||||
</sec>
|
||||
<sec id="research-design-nb-article">
|
||||
<title>Research Design</title>
|
||||
<sec id="data-nb-article">
|
||||
<title>Data</title>
|
||||
</sec>
|
||||
<sec id="method-nb-article">
|
||||
<title>Method</title>
|
||||
</sec>
|
||||
</sec>
|
||||
<sec id="results-nb-article">
|
||||
<title>Results</title>
|
||||
</sec>
|
||||
<sec id="discussion-nb-article">
|
||||
<title>Discussion</title>
|
||||
</sec>
|
||||
<sec id="conclusion-nb-article">
|
||||
<title>Conclusion</title>
|
||||
<p>Conclusions here.</p>
|
||||
</sec>
|
||||
<sec id="acknowlegments-nb-article">
|
||||
<title>Acknowlegments</title>
|
||||
<p>I would like to thank my advisor, [Advisor Name], for his guidance,
|
||||
constructive feedback, and steady support throughout this project. His
|
||||
expertise and encouragement were invaluable in shaping both the
|
||||
research and this publication.</p>
|
||||
</sec>
|
||||
<sec id="data-availability-nb-article">
|
||||
<title>Data availability</title>
|
||||
<p>Materials, Data and Code are made available at a public
|
||||
OSF-repository that can be accessed here:</p>
|
||||
<list list-type="bullet">
|
||||
<list-item>
|
||||
<p>https://osf.io/</p>
|
||||
</list-item>
|
||||
</list>
|
||||
</sec>
|
||||
<sec id="funding-nb-article">
|
||||
<title>Funding</title>
|
||||
<p>This research received no external funding.</p>
|
||||
</sec>
|
||||
<sec id="conflict-of-interest-nb-article">
|
||||
<title>Conflict of interest</title>
|
||||
<p>The authors declare no conflict of interest.</p>
|
||||
</sec>
|
||||
<sec id="author-biography-nb-article">
|
||||
<title>Author Biography</title>
|
||||
<p>Jane doe is a scientist.</p>
|
||||
</sec>
|
||||
<sec id="bibliography-nb-article">
|
||||
<title>Bibliography</title>
|
||||
</sec>
|
||||
</body>
|
||||
|
||||
|
||||
|
||||
<back>
|
||||
</back>
|
||||
|
||||
|
||||
</sub-article>
|
||||
|
||||
</article>
|
||||
@@ -0,0 +1,19 @@
|
||||
@article{knuth84,
|
||||
author = {Knuth, Donald E.},
|
||||
title = {Literate Programming},
|
||||
year = {1984},
|
||||
issue_date = {May 1984},
|
||||
publisher = {Oxford University Press, Inc.},
|
||||
address = {USA},
|
||||
volume = {27},
|
||||
number = {2},
|
||||
issn = {0010-4620},
|
||||
url = {https://doi.org/10.1093/comjnl/27.2.97},
|
||||
doi = {10.1093/comjnl/27.2.97},
|
||||
journal = {Comput. J.},
|
||||
month = may,
|
||||
pages = {97–111},
|
||||
numpages = {15}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
project:
|
||||
output-dir: _output/anonymized
|
||||
|
||||
metadata-files:
|
||||
- ../resources/author_profiles/_quarto-anonymized.yml
|
||||
@@ -0,0 +1,5 @@
|
||||
project:
|
||||
output-dir: _output/default
|
||||
|
||||
metadata-files:
|
||||
- ../resources/author_profiles/_quarto-default.yml
|
||||
@@ -0,0 +1,59 @@
|
||||
project:
|
||||
type: manuscript
|
||||
|
||||
manuscript:
|
||||
article: index.qmd
|
||||
|
||||
format:
|
||||
docx:
|
||||
prefer-html: true
|
||||
toc: true
|
||||
toc-depth: 3
|
||||
lot: true
|
||||
lof: true
|
||||
fig-dpi: 300
|
||||
reference-doc: ../resources/custom-reference-doc.docx
|
||||
jats: default
|
||||
pdf: default
|
||||
|
||||
profile:
|
||||
default: default
|
||||
|
||||
execute:
|
||||
freeze: auto
|
||||
echo: false
|
||||
warning: false
|
||||
|
||||
bibliography: ../resources/references.bib
|
||||
citeproc: true
|
||||
citation-package: none
|
||||
reference-section-title: Bibliography
|
||||
link-citations: true
|
||||
# Check https://www.zotero.org/styles/ for more styles
|
||||
csl: ../resources/apa7.csl
|
||||
|
||||
lang: en-US
|
||||
|
||||
keywords:
|
||||
- Open Science Practices
|
||||
- Open Access
|
||||
- Open Data
|
||||
- Open Materials
|
||||
- Preregistration
|
||||
- Text Mining
|
||||
- Machine Learning Classification
|
||||
- AI-assisted Coding
|
||||
- Replication
|
||||
|
||||
abstract: |
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||
|
||||
header-includes: |
|
||||
\usepackage{pdfpages}
|
||||
\pagenumbering{gobble}
|
||||
\usepackage{booktabs}
|
||||
\usepackage{caption}
|
||||
\DeclareCaptionLabelSeparator{period-newline}{.\par}
|
||||
\usepackage{setspace}
|
||||
\usepackage{float} % enables [H]
|
||||
\usepackage[section]{placeins} % <- add [section]
|
||||
@@ -0,0 +1 @@
|
||||
Put your figures in this folder.
|
||||
@@ -0,0 +1 @@
|
||||
Put your figures in this folder.
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Lorem Ipsum
|
||||
---
|
||||
|
||||
```{=latex}
|
||||
% keeps the title page numbers empty
|
||||
\pagestyle{empty}
|
||||
\newpage
|
||||
\pagenumbering{arabic}
|
||||
\pagestyle{headings}
|
||||
\onehalfspacing % 1.5 line spacing from here on
|
||||
```
|
||||
|
||||
# Introduction
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas ac egestas ipsum. Vivamus bibendum accumsan nisi ac porta. Aenean efficitur sit amet lacus sollicitudin condimentum. Cras eros ipsum, euismod in odio pulvinar, imperdiet semper enim. Proin lobortis molestie mi eu blandit. Etiam eget velit laoreet, fringilla ligula at, fermentum velit. Sed mollis justo vitae quam pharetra, luctus aliquam neque vulputate. In tincidunt purus at viverra accumsan. Vivamus scelerisque suscipit turpis vitae pellentesque. Aliquam et euismod mauris. Mauris vitae erat velit. Aliquam id tincidunt est. Donec consequat est tellus, vitae sagittis tortor laoreet ut. Morbi sed dolor tristique lacus interdum imperdiet eget vitae elit. Cras ut eleifend turpis.
|
||||
|
||||
In efficitur efficitur magna. Praesent id facilisis ex, ut eleifend mi. Sed id felis dapibus, vestibulum elit ac, ultricies nibh. Maecenas accumsan rhoncus rhoncus. Duis quis erat at risus convallis pretium. Sed eget varius nulla, ut sagittis felis. Donec magna justo, sagittis et feugiat a, iaculis in felis. Nam sed aliquam tellus. Fusce non metus quis enim condimentum gravida laoreet non mi. Fusce efficitur laoreet libero, id tincidunt dui tristique molestie. In nisi dolor, pulvinar eu massa at, tempor posuere libero. Suspendisse aliquam dui luctus justo rutrum, in feugiat urna elementum. Quisque rhoncus erat et aliquet dictum. Quisque rutrum nibh sit amet malesuada pretium. Curabitur eget malesuada sapien.
|
||||
|
||||
Fusce faucibus risus sed feugiat lacinia. Nulla nec augue ut nulla facilisis efficitur. Fusce volutpat condimentum tincidunt. Curabitur sodales lacus vel iaculis rutrum. Donec ipsum massa, sagittis vitae ornare sit amet, pulvinar et dui. Duis aliquam laoreet consequat. Nulla rhoncus turpis mauris, eget mollis ipsum malesuada id. Vestibulum dictum erat id nibh laoreet, et facilisis lacus mollis. Aliquam libero neque, efficitur suscipit interdum eget, tempus in nisi. Mauris at convallis metus. Sed in viverra dolor. Etiam suscipit nisl id vestibulum sodales. Phasellus dolor mi, elementum a scelerisque vestibulum, blandit sit amet ex. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris risus nunc, blandit ac leo eget, pulvinar rutrum tellus. Quisque vel congue est.
|
||||
|
||||
Duis scelerisque tempus convallis. Donec condimentum gravida augue, sit amet hendrerit ligula dictum nec. Nam elementum eu erat id dignissim. Vestibulum pretium, nisl tempus cursus luctus, nisl lacus blandit odio, nec dictum massa dui sollicitudin nisi. Nam ornare eros eu leo cursus egestas. Nulla scelerisque metus a dictum condimentum. Aliquam scelerisque sapien eget eros efficitur, at maximus urna tincidunt.
|
||||
|
||||
Morbi in tincidunt urna. In lacinia, quam non iaculis vulputate, elit ante placerat nisl, nec ornare justo eros fermentum elit. Integer id semper lectus. Ut ac tincidunt leo. Suspendisse nec nisi imperdiet, auctor dui sed, eleifend arcu. In nec turpis volutpat orci pulvinar lacinia. Sed nisi diam, dictum at ex nec, blandit maximus dolor. Ut vitae ullamcorper felis. Let's cite @knuth84.
|
||||
|
||||
# Background
|
||||
|
||||
Phasellus tempor et nulla et maximus. Nulla mattis placerat lacus, sed bibendum nibh ultricies rhoncus. Maecenas ex libero, gravida at feugiat in, hendrerit sed libero. Proin semper orci sem, sit amet aliquam urna elementum mattis. Morbi lacinia euismod metus sed sollicitudin. Sed interdum maximus turpis, a ultrices velit pharetra non. Etiam eu lorem ligula. Pellentesque leo magna, facilisis et magna sed, porta vestibulum augue. Pellentesque interdum neque et nisl gravida, id luctus ipsum eleifend.
|
||||
|
||||
# Research Design
|
||||
|
||||
## Data
|
||||
|
||||
## Method
|
||||
|
||||
# Results
|
||||
|
||||
# Discussion
|
||||
|
||||
# Conclusion
|
||||
|
||||
Conclusions here.
|
||||
|
||||
# Acknowlegments {.unnumbered}
|
||||
|
||||
I would like to thank my advisor, [Advisor Name], for his guidance, constructive feedback, and steady support throughout this project. His expertise and encouragement were invaluable in shaping both the research and this publication.
|
||||
|
||||
# Data availability {.unnumbered}
|
||||
|
||||
Materials, Data and Code are made available at a public OSF-repository that can be accessed here:
|
||||
|
||||
- https://osf.io/
|
||||
|
||||
# Funding {.unnumbered}
|
||||
|
||||
This research received no external funding.
|
||||
|
||||
# Conflict of interest {.unnumbered}
|
||||
|
||||
The authors declare no conflict of interest.
|
||||
|
||||
# Author Biography {.unnumbered}
|
||||
|
||||
Jane doe is a scientist.
|
||||
|
||||
# Bibliography {.unnumbered}
|
||||
|
||||
::: {#refs}
|
||||
:::
|
||||
@@ -0,0 +1,103 @@
|
||||
# ScientificManuscriptTemplate
|
||||
|
||||
A multi-project Quarto template for a manuscript, a research report, and supplements, sharing one set of resources (bibliography, CSL style, author/affiliation data, reference docx) and rendered via [Task](https://taskfile.dev).
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Quarto](https://quarto.org)
|
||||
- [Task](https://taskfile.dev)
|
||||
- Python 3 (used for zipping/cleaning)
|
||||
|
||||
Make sure that Quarto, Task and Python are on your PATH.
|
||||
|
||||
## How to use
|
||||
|
||||
Clone the repo and edit `*.qmd` files, update author information in `resources/author_profiles/_quarto-default.yml` include references in `resources/references.bib` and render all projects using `task finalize`.
|
||||
|
||||
In any arch-based Linux distro, run `go-task finalize` instead.
|
||||
|
||||
More on how to include more detail in the front matter can be found in the official [quarto manual](https://quarto.org/docs/authoring/front-matter.html). Their quick introduction into [Markdown Basics](https://quarto.org/docs/authoring/markdown-basics.html) is also worth reading.
|
||||
|
||||
## Project layout
|
||||
|
||||
Each of `Manuscript/`, `ResearchReport/`, and `Supplements/` is its own Quarto project with its own `_quarto.yml`, `index.qmd`, `data/`, and `figures/` folders.
|
||||
|
||||
```
|
||||
ScientificManuscriptTemplate/
|
||||
├── Manuscript/ ← the paper itself
|
||||
├── ResearchReport/ ← longer-form report
|
||||
├── Supplements/ ← supplementary materialss
|
||||
├── resources/ ← shared across all three projects
|
||||
│ ├── references.bib ← biblatex references
|
||||
│ ├── apa7.csl ← citation style, grab from https://www.zotero.org/styles/
|
||||
│ ├── custom-reference-doc.docx ← docx template
|
||||
│ └── author_profiles/
|
||||
│ ├── _quarto-default.yml ← real author/affiliation metadata
|
||||
│ └── _quarto-anonymized.yml ← anonymized-profile metadata
|
||||
├── Taskfile.yml ← run commands from here for the whole project
|
||||
└── finalized/ ← created by `task finalize`, date-stamped output
|
||||
```
|
||||
|
||||
## Writing content
|
||||
|
||||
Edit the `.qmd` file(s) in whichever project you're working on (e.g. `Manuscript/index.qmd`). Put source data in that project's `data/` folder and images/plots in `figures/`. These are per-project, not shared.
|
||||
|
||||
Shared resources live once in `resources/` and are referenced via relative paths from each project's `_quarto.yml` (bibliography, CSL, reference docx, author metadata) - edit them there, not inside an individual project folder, so all three projects stay in sync.
|
||||
|
||||
## Authors and affiliations
|
||||
|
||||
Real author names and affiliations are defined once in `resources/author_profiles/_quarto-default.yml`. The anonymized counterpart lives in `_quarto-anonymized.yml` in the same older. Update uthor info there. It's picked up automatically by every project.
|
||||
|
||||
## Rendering
|
||||
|
||||
Each project has its own profile pair - `default` (real author info) and `anonymized` (for blind peer review) - selected via Quarto's `--profile` flag, which the Task commands below already handle for you.
|
||||
|
||||
**Render one project**, from inside that project's folder:
|
||||
|
||||
```
|
||||
cd Manuscript
|
||||
task render # default profile, all formats
|
||||
task render:anonymized # anonymized profile, all formats
|
||||
task render:html # single format, default profile
|
||||
task render:docx
|
||||
task render:pdf
|
||||
task render:jats
|
||||
```
|
||||
|
||||
**Or run any project's task from the repo root**, using its namespace:
|
||||
|
||||
```
|
||||
task manuscript:render
|
||||
task researchreport:render:anonymized
|
||||
task supplements:render:docx
|
||||
```
|
||||
|
||||
**Render everything at once**, from the repo root:
|
||||
|
||||
```
|
||||
task render:all # all three projects, default profile
|
||||
task render:all:anonymized # all three projects, anonymized profile
|
||||
```
|
||||
|
||||
## Packaging and finalizing
|
||||
|
||||
```
|
||||
task manuscript:package # zip Manuscript's default output
|
||||
task manuscript:package:data # zip Manuscript/data into data.zip
|
||||
task package:all # package every project, both profiles, plus data
|
||||
task finalize # render everything, package everything,
|
||||
# collect it all into finalized/<today's date>/
|
||||
```
|
||||
|
||||
`task finalize` is the one command to run before submitting: it renders both profiles for all three projects, zips each project's output, and copies every zip into a fresh `finalized/YYYY-MM-DD/` folder so you have a clean, dated record of exactly what was submitted.
|
||||
|
||||
## Cleaning up
|
||||
|
||||
> [!WARNING]
|
||||
> Be careful using this: the command also cleans caches, meaning that all computations will have to be re-evaluated.
|
||||
|
||||
```
|
||||
task manuscript:clean # remove Manuscript's _output, .quarto, and stray cache folders
|
||||
task clean:all # same, for all three projects
|
||||
```
|
||||
@@ -0,0 +1,2 @@
|
||||
/.quarto/
|
||||
**/*.quarto_ipynb
|
||||
@@ -0,0 +1,61 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
PROJECT_NAME: ResearchReport
|
||||
PYTHON: '{{if eq OS "windows"}}python{{else}}python3{{end}}'
|
||||
UTILS: "../resources/scripts/task_utils.py"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
desc: "List available tasks"
|
||||
cmd: task --list
|
||||
|
||||
render:
|
||||
desc: "Render with the default (non-anonymized) profile, all formats"
|
||||
cmd: quarto render --profile=default --output-dir _output/default
|
||||
|
||||
render:anonymized:
|
||||
desc: "Render with the anonymized profile, all formats"
|
||||
cmd: quarto render --profile=anonymized --output-dir _output/anonymized
|
||||
|
||||
render:html:
|
||||
desc: "Render to HTML only (default profile)"
|
||||
cmd: quarto render --profile=default --to html --output-dir _output/default
|
||||
|
||||
render:docx:
|
||||
desc: "Render to docx only (default profile)"
|
||||
cmd: quarto render --profile=default --to docx --output-dir _output/default
|
||||
|
||||
render:pdf:
|
||||
desc: "Render to PDF only (default profile) - requires pdf format configured in _quarto.yml"
|
||||
cmd: quarto render --profile=default --to pdf --output-dir _output/default
|
||||
|
||||
package:
|
||||
desc: "Zip the default-profile rendered output into ResearchReport-default.zip"
|
||||
deps: [render]
|
||||
cmd: "{{.PYTHON}} {{.UTILS}} zip _output/default {{.PROJECT_NAME}}-default.zip"
|
||||
sources:
|
||||
- _output/default/**/*
|
||||
generates:
|
||||
- "{{.PROJECT_NAME}}-default.zip"
|
||||
|
||||
package:anonymized:
|
||||
desc: "Zip the anonymized-profile rendered output into ResearchReport-anonymized.zip"
|
||||
deps: [render:anonymized]
|
||||
cmd: "{{.PYTHON}} {{.UTILS}} zip _output/anonymized {{.PROJECT_NAME}}-anonymized.zip"
|
||||
sources:
|
||||
- _output/anonymized/**/*
|
||||
generates:
|
||||
- "{{.PROJECT_NAME}}-anonymized.zip"
|
||||
|
||||
package:data:
|
||||
desc: "Zip the data/ folder into data.zip (skips with a warning if data/ doesn't exist)"
|
||||
cmd: "{{.PYTHON}} {{.UTILS}} zip-if-exists data data.zip"
|
||||
sources:
|
||||
- data/**/*
|
||||
generates:
|
||||
- data.zip
|
||||
|
||||
clean:
|
||||
desc: "Remove _output, .quarto, and stray *_files/*_cache folders"
|
||||
cmd: "{{.PYTHON}} {{.UTILS}} clean-project ."
|
||||
@@ -0,0 +1,5 @@
|
||||
project:
|
||||
output-dir: _output/anonymized
|
||||
|
||||
metadata-files:
|
||||
- ../resources/author_profiles/_quarto-anonymized.yml
|
||||
@@ -0,0 +1,5 @@
|
||||
project:
|
||||
output-dir: _output/default
|
||||
|
||||
metadata-files:
|
||||
- ../resources/author_profiles/_quarto-default.yml
|
||||
@@ -0,0 +1,26 @@
|
||||
project:
|
||||
type: book
|
||||
output-dir: _output
|
||||
|
||||
book:
|
||||
title: "Report Title"
|
||||
author: "Norah Jones"
|
||||
date: "17.6.2026"
|
||||
chapters:
|
||||
- index.qmd
|
||||
- intro.qmd
|
||||
- summary.qmd
|
||||
- references.qmd
|
||||
|
||||
bibliography: references.bib
|
||||
|
||||
format:
|
||||
html:
|
||||
theme:
|
||||
- cosmo
|
||||
- brand
|
||||
pdf:
|
||||
documentclass: scrreprt
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1 @@
|
||||
Put your figures in this folder.
|
||||
@@ -0,0 +1 @@
|
||||
Put your figures in this folder.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Preface {.unnumbered}
|
||||
|
||||
This is a Quarto book.
|
||||
|
||||
To learn more about Quarto books visit <https://quarto.org/docs/books>.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Introduction
|
||||
|
||||
This is a book created from markdown and executable code.
|
||||
|
||||
See @knuth84 for additional discussion of literate programming.
|
||||
@@ -0,0 +1,19 @@
|
||||
@article{knuth84,
|
||||
author = {Knuth, Donald E.},
|
||||
title = {Literate Programming},
|
||||
year = {1984},
|
||||
issue_date = {May 1984},
|
||||
publisher = {Oxford University Press, Inc.},
|
||||
address = {USA},
|
||||
volume = {27},
|
||||
number = {2},
|
||||
issn = {0010-4620},
|
||||
url = {https://doi.org/10.1093/comjnl/27.2.97},
|
||||
doi = {10.1093/comjnl/27.2.97},
|
||||
journal = {Comput. J.},
|
||||
month = may,
|
||||
pages = {97–111},
|
||||
numpages = {15}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# References {.unnumbered}
|
||||
|
||||
::: {#refs}
|
||||
:::
|
||||
@@ -0,0 +1,3 @@
|
||||
# Summary
|
||||
|
||||
In summary, this book has no content whatsoever.
|
||||
@@ -0,0 +1,2 @@
|
||||
/.quarto/
|
||||
**/*.quarto_ipynb
|
||||
@@ -0,0 +1,62 @@
|
||||
# Supplements/Taskfile.yml
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
PROJECT_NAME: Supplements
|
||||
PYTHON: '{{if eq OS "windows"}}python{{else}}python3{{end}}'
|
||||
UTILS: "../resources/scripts/task_utils.py"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
desc: "List available tasks"
|
||||
cmd: task --list
|
||||
|
||||
render:
|
||||
desc: "Render with the default (non-anonymized) profile, all formats"
|
||||
cmd: quarto render --profile=default --output-dir _output/default
|
||||
|
||||
render:anonymized:
|
||||
desc: "Render with the anonymized profile, all formats"
|
||||
cmd: quarto render --profile=anonymized --output-dir _output/anonymized
|
||||
|
||||
render:html:
|
||||
desc: "Render to HTML only (default profile)"
|
||||
cmd: quarto render --profile=default --to html --output-dir _output/default
|
||||
|
||||
render:docx:
|
||||
desc: "Render to docx only (default profile)"
|
||||
cmd: quarto render --profile=default --to docx --output-dir _output/default
|
||||
|
||||
render:pdf:
|
||||
desc: "Render to PDF only (default profile) - requires pdf format configured in _quarto.yml"
|
||||
cmd: quarto render --profile=default --to pdf --output-dir _output/default
|
||||
|
||||
package:
|
||||
desc: "Zip the default-profile rendered output into Supplements-default.zip"
|
||||
deps: [render]
|
||||
cmd: "{{.PYTHON}} {{.UTILS}} zip _output/default {{.PROJECT_NAME}}-default.zip"
|
||||
sources:
|
||||
- _output/default/**/*
|
||||
generates:
|
||||
- "{{.PROJECT_NAME}}-default.zip"
|
||||
|
||||
package:anonymized:
|
||||
desc: "Zip the anonymized-profile rendered output into Supplements-anonymized.zip"
|
||||
deps: [render:anonymized]
|
||||
cmd: "{{.PYTHON}} {{.UTILS}} zip _output/anonymized {{.PROJECT_NAME}}-anonymized.zip"
|
||||
sources:
|
||||
- _output/anonymized/**/*
|
||||
generates:
|
||||
- "{{.PROJECT_NAME}}-anonymized.zip"
|
||||
|
||||
package:data:
|
||||
desc: "Zip the data/ folder into data.zip (skips with a warning if data/ doesn't exist)"
|
||||
cmd: "{{.PYTHON}} {{.UTILS}} zip-if-exists data data.zip"
|
||||
sources:
|
||||
- data/**/*
|
||||
generates:
|
||||
- data.zip
|
||||
|
||||
clean:
|
||||
desc: "Remove _output, .quarto, and stray *_files/*_cache folders"
|
||||
cmd: "{{.PYTHON}} {{.UTILS}} clean-project ."
|
||||
@@ -0,0 +1,5 @@
|
||||
project:
|
||||
output-dir: _output/anonymized
|
||||
|
||||
metadata-files:
|
||||
- ../resources/author_profiles/_quarto-anonymized.yml
|
||||
@@ -0,0 +1,5 @@
|
||||
project:
|
||||
output-dir: _output/default
|
||||
|
||||
metadata-files:
|
||||
- ../resources/author_profiles/_quarto-default.yml
|
||||
@@ -0,0 +1,22 @@
|
||||
project:
|
||||
type: manuscript
|
||||
output-dir: _output
|
||||
|
||||
manuscript:
|
||||
article: index.qmd
|
||||
|
||||
format:
|
||||
html:
|
||||
comments:
|
||||
hypothesis: true
|
||||
docx: default
|
||||
jats: default
|
||||
|
||||
# (other formats)
|
||||
# pdf: default
|
||||
|
||||
execute:
|
||||
freeze: true
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Put your figures in this folder.
|
||||
@@ -0,0 +1 @@
|
||||
Put your figures in this folder.
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: Supplementary Materials
|
||||
authors:
|
||||
- name: Norah Jones
|
||||
affiliation: The University
|
||||
roles: writing
|
||||
corresponding: true
|
||||
bibliography: references.bib
|
||||
---
|
||||
|
||||
## Section
|
||||
This is a simple placeholder for the manuscript's main document [@knuth84].
|
||||
@@ -0,0 +1,19 @@
|
||||
@article{knuth84,
|
||||
author = {Knuth, Donald E.},
|
||||
title = {Literate Programming},
|
||||
year = {1984},
|
||||
issue_date = {May 1984},
|
||||
publisher = {Oxford University Press, Inc.},
|
||||
address = {USA},
|
||||
volume = {27},
|
||||
number = {2},
|
||||
issn = {0010-4620},
|
||||
url = {https://doi.org/10.1093/comjnl/27.2.97},
|
||||
doi = {10.1093/comjnl/27.2.97},
|
||||
journal = {Comput. J.},
|
||||
month = may,
|
||||
pages = {97–111},
|
||||
numpages = {15}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
PYTHON: '{{if eq OS "windows"}}python{{else}}python3{{end}}'
|
||||
DATE:
|
||||
sh: '{{if eq OS "windows"}}python{{else}}python3{{end}} ./resources/scripts/task_utils.py today'
|
||||
FINALIZED_DIR: "finalized/{{.DATE}}"
|
||||
|
||||
includes:
|
||||
manuscript:
|
||||
taskfile: ./Manuscript/Taskfile.yml
|
||||
dir: ./Manuscript
|
||||
researchreport:
|
||||
taskfile: ./ResearchReport/Taskfile.yml
|
||||
dir: ./ResearchReport
|
||||
supplements:
|
||||
taskfile: ./Supplements/Taskfile.yml
|
||||
dir: ./Supplements
|
||||
|
||||
tasks:
|
||||
default:
|
||||
desc: "List available tasks"
|
||||
cmd: task --list
|
||||
|
||||
render:all:
|
||||
desc: "Render all three projects with the default profile"
|
||||
cmds:
|
||||
- task: manuscript:render
|
||||
- task: researchreport:render
|
||||
- task: supplements:render
|
||||
|
||||
render:all:anonymized:
|
||||
desc: "Render all three projects with the anonymized profile"
|
||||
cmds:
|
||||
- task: manuscript:render:anonymized
|
||||
- task: researchreport:render:anonymized
|
||||
- task: supplements:render:anonymized
|
||||
|
||||
package:all:
|
||||
desc: "Package every project: default zip, anonymized zip, and data zip"
|
||||
cmds:
|
||||
- task: manuscript:package
|
||||
- task: manuscript:package:anonymized
|
||||
- task: manuscript:package:data
|
||||
- task: researchreport:package
|
||||
- task: researchreport:package:anonymized
|
||||
- task: researchreport:package:data
|
||||
- task: supplements:package
|
||||
- task: supplements:package:anonymized
|
||||
- task: supplements:package:data
|
||||
|
||||
clean:all:
|
||||
desc: "Clean every project's rendered output and Quarto cache"
|
||||
cmds:
|
||||
- task: manuscript:clean
|
||||
- task: researchreport:clean
|
||||
- task: supplements:clean
|
||||
- task: clean:zips
|
||||
|
||||
clean:zips:
|
||||
desc: "Clean every project's zip-packages within the project folders (but keep rendered output)"
|
||||
cmds:
|
||||
- cmd: "{{.PYTHON}} ./resources/scripts/task_utils.py clean-zips Manuscript"
|
||||
- cmd: "{{.PYTHON}} ./resources/scripts/task_utils.py clean-zips ResearchReport"
|
||||
- cmd: "{{.PYTHON}} ./resources/scripts/task_utils.py clean-zips Supplements"
|
||||
|
||||
finalize:
|
||||
desc: "Full pipeline: render both profiles, package everything, collect into finalized/<date>/"
|
||||
cmds:
|
||||
- task: render:all
|
||||
- task: render:all:anonymized
|
||||
- task: package:all
|
||||
- task: collect
|
||||
- cmd: echo "Finalized bundle ready at {{.FINALIZED_DIR}}"
|
||||
|
||||
collect:
|
||||
internal: false
|
||||
desc: "Copy all project zips into the date-stamped finalized folder"
|
||||
cmds:
|
||||
- cmd: '{{if eq OS "windows"}}if not exist "{{.FINALIZED_DIR}}" mkdir "{{.FINALIZED_DIR}}"{{else}}mkdir -p "{{.FINALIZED_DIR}}"{{end}}'
|
||||
- cmd: "{{.PYTHON}} ./resources/scripts/task_utils.py copy-if-exists Manuscript/Manuscript-default.zip {{.FINALIZED_DIR}}/Manuscript-default.zip"
|
||||
- cmd: "{{.PYTHON}} ./resources/scripts/task_utils.py copy-if-exists Manuscript/Manuscript-anonymized.zip {{.FINALIZED_DIR}}/Manuscript-anonymized.zip"
|
||||
- cmd: "{{.PYTHON}} ./resources/scripts/task_utils.py copy-if-exists Manuscript/data.zip {{.FINALIZED_DIR}}/Manuscript-data.zip"
|
||||
- cmd: "{{.PYTHON}} ./resources/scripts/task_utils.py copy-if-exists ResearchReport/ResearchReport-default.zip {{.FINALIZED_DIR}}/ResearchReport-default.zip"
|
||||
- cmd: "{{.PYTHON}} ./resources/scripts/task_utils.py copy-if-exists ResearchReport/ResearchReport-anonymized.zip {{.FINALIZED_DIR}}/ResearchReport-anonymized.zip"
|
||||
- cmd: "{{.PYTHON}} ./resources/scripts/task_utils.py copy-if-exists ResearchReport/data.zip {{.FINALIZED_DIR}}/ResearchReport-data.zip"
|
||||
- cmd: "{{.PYTHON}} ./resources/scripts/task_utils.py copy-if-exists Supplements/Supplements-default.zip {{.FINALIZED_DIR}}/Supplements-default.zip"
|
||||
- cmd: "{{.PYTHON}} ./resources/scripts/task_utils.py copy-if-exists Supplements/Supplements-anonymized.zip {{.FINALIZED_DIR}}/Supplements-anonymized.zip"
|
||||
- cmd: "{{.PYTHON}} ./resources/scripts/task_utils.py copy-if-exists Supplements/data.zip {{.FINALIZED_DIR}}/Supplements-data.zip"
|
||||
+2273
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,176 @@
|
||||
--[[
|
||||
affiliation-blocks – generate title components
|
||||
|
||||
Copyright © 2017–2021 Albert Krewinkel
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
]]
|
||||
local List = require 'pandoc.List'
|
||||
local utils = require 'pandoc.utils'
|
||||
local stringify = utils.stringify
|
||||
|
||||
local default_marks
|
||||
local default_marks = {
|
||||
corresponding_author = FORMAT == 'latex'
|
||||
and {pandoc.RawInline('latex', '*')}
|
||||
or {pandoc.Str '✉'},
|
||||
equal_contributor = FORMAT == 'latex'
|
||||
and {pandoc.RawInline('latex', '$\\dagger{}$')}
|
||||
or {pandoc.Str '*'},
|
||||
}
|
||||
|
||||
local function intercalate(lists, elem)
|
||||
local result = List:new{}
|
||||
for i = 1, (#lists - 1) do
|
||||
result:extend(lists[i])
|
||||
result:extend(elem)
|
||||
end
|
||||
if #lists > 0 then
|
||||
result:extend(lists[#lists])
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
--- Check whether the given author is a corresponding author
|
||||
local function is_corresponding_author(author)
|
||||
return author.correspondence and author.email
|
||||
end
|
||||
|
||||
--- Create inlines for a single author (includes all author notes)
|
||||
local function author_inline_generator (get_mark)
|
||||
return function (author)
|
||||
local author_marks = List:new{}
|
||||
if author.equal_contributor then
|
||||
author_marks[#author_marks + 1] = get_mark 'equal_contributor'
|
||||
end
|
||||
local idx_str
|
||||
for _, idx in ipairs(author.institute) do
|
||||
if type(idx) ~= 'table' then
|
||||
idx_str = tostring(idx)
|
||||
else
|
||||
idx_str = stringify(idx)
|
||||
end
|
||||
author_marks[#author_marks + 1] = {pandoc.Str(idx_str)}
|
||||
end
|
||||
if is_corresponding_author(author) then
|
||||
author_marks[#author_marks + 1] = get_mark 'corresponding_author'
|
||||
end
|
||||
local res = List.clone(author.name)
|
||||
res[#res + 1] = pandoc.Superscript(intercalate(author_marks, {pandoc.Str ','}))
|
||||
return res
|
||||
end
|
||||
end
|
||||
|
||||
local function is_equal_contributor (author)
|
||||
return author.equal_contributor
|
||||
end
|
||||
|
||||
--- Create equal contributors note.
|
||||
local function create_equal_contributors_block(authors, mark)
|
||||
local has_equal_contribs = List:new(authors):find_if(is_equal_contributor)
|
||||
if not has_equal_contribs then
|
||||
return nil
|
||||
end
|
||||
local contributors = {
|
||||
pandoc.Superscript(mark'equal_contributor'),
|
||||
pandoc.Space(),
|
||||
pandoc.Str 'These authors contributed equally to this work.'
|
||||
}
|
||||
return List:new{pandoc.Para(contributors)}
|
||||
end
|
||||
|
||||
--- Generate a block list all affiliations, marked with arabic numbers.
|
||||
local function create_affiliations_blocks(affiliations)
|
||||
local affil_lines = List:new(affiliations):map(
|
||||
function (affil, i)
|
||||
local num_inlines = List:new{
|
||||
pandoc.Superscript{pandoc.Str(tostring(i))},
|
||||
pandoc.Space()
|
||||
}
|
||||
return num_inlines .. affil.name
|
||||
end
|
||||
)
|
||||
return {pandoc.Para(intercalate(affil_lines, {pandoc.LineBreak()}))}
|
||||
end
|
||||
|
||||
--- Generate a block element containing the correspondence information
|
||||
local function create_correspondence_blocks(authors, mark)
|
||||
local corresponding_authors = List:new{}
|
||||
for _, author in ipairs(authors) do
|
||||
if is_corresponding_author(author) then
|
||||
local mailto = 'mailto:' .. pandoc.utils.stringify(author.email)
|
||||
local author_with_mail = List:new(
|
||||
author.name .. List:new{pandoc.Space(), pandoc.Str '<'} ..
|
||||
author.email .. List:new{pandoc.Str '>'}
|
||||
)
|
||||
local link = pandoc.Link(author_with_mail, mailto)
|
||||
table.insert(corresponding_authors, {link})
|
||||
end
|
||||
end
|
||||
if #corresponding_authors == 0 then
|
||||
return nil
|
||||
end
|
||||
local correspondence = List:new{
|
||||
pandoc.Superscript(mark'corresponding_author'),
|
||||
pandoc.Space(),
|
||||
pandoc.Str'Correspondence:',
|
||||
pandoc.Space()
|
||||
}
|
||||
local sep = List:new{pandoc.Str',', pandoc.Space()}
|
||||
return {
|
||||
pandoc.Para(correspondence .. intercalate(corresponding_authors, sep))
|
||||
}
|
||||
end
|
||||
|
||||
--- Generate a list of inlines containing all authors.
|
||||
local function create_authors_inlines(authors, mark)
|
||||
local inlines_generator = author_inline_generator(mark)
|
||||
local inlines = List:new(authors):map(inlines_generator)
|
||||
local and_str = List:new{pandoc.Space(), pandoc.Str'and', pandoc.Space()}
|
||||
|
||||
local last_author = inlines[#inlines]
|
||||
inlines[#inlines] = nil
|
||||
local result = intercalate(inlines, {pandoc.Str ',', pandoc.Space()})
|
||||
if #authors > 1 then
|
||||
result:extend(List:new{pandoc.Str ","} .. and_str)
|
||||
end
|
||||
result:extend(last_author)
|
||||
return result
|
||||
end
|
||||
|
||||
return {
|
||||
{
|
||||
Pandoc = function (doc)
|
||||
local meta = doc.meta
|
||||
local body = List:new{}
|
||||
|
||||
local mark = function (mark_name) return default_marks[mark_name] end
|
||||
|
||||
body:extend(create_equal_contributors_block(doc.meta.author, mark) or {})
|
||||
body:extend(create_affiliations_blocks(doc.meta.institute) or {})
|
||||
body:extend(create_correspondence_blocks(doc.meta.author, mark) or {})
|
||||
body:extend(doc.blocks)
|
||||
|
||||
-- Overwrite authors with formatted values. We use a single, formatted
|
||||
-- string for most formats. LaTeX output, however, looks nicer if we
|
||||
-- provide a authors as a list.
|
||||
meta.author = FORMAT:match 'latex'
|
||||
and pandoc.MetaList(doc.meta.author):map(author_inline_generator(mark))
|
||||
or pandoc.MetaInlines(create_authors_inlines(doc.meta.author, mark))
|
||||
-- Institute info is now baked into the affiliations block.
|
||||
meta.institute = nil
|
||||
|
||||
return pandoc.Pandoc(body, meta)
|
||||
end
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
authors:
|
||||
- name: Anonymous
|
||||
@@ -0,0 +1,27 @@
|
||||
authors:
|
||||
- name: John Doe
|
||||
affiliations:
|
||||
- ref: jdct
|
||||
corresponding: true
|
||||
email: john.doe@jdct.edu
|
||||
orcid: 0000-1111-2222-3333
|
||||
equal-contributor: true
|
||||
- name: John Roe
|
||||
affiliations:
|
||||
- ref: jdct
|
||||
orcid: 0000-3333-2222-1111
|
||||
- name: Jane Roe
|
||||
affiliations:
|
||||
- ref: jdct
|
||||
- ref: iot
|
||||
orcid: 0000-2222-1111-3333
|
||||
equal-contributor: true
|
||||
|
||||
affiliations:
|
||||
- id: jdct
|
||||
name: John Doe Center for Technology, John Doe University, Doetown, Germany.
|
||||
- id: iot
|
||||
name: Institute of Technology, John Doe University, Doetown, Germany.
|
||||
|
||||
filters:
|
||||
- ../resources/authors_block/authors-block.lua
|
||||
@@ -0,0 +1,71 @@
|
||||
--[[
|
||||
authors-block – affiliations block extension for quarto
|
||||
|
||||
Copyright (c) 2023 Lorenz A. Kapsner
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
]]
|
||||
|
||||
local List = require 'pandoc.List'
|
||||
|
||||
-- [import]
|
||||
local from_utils = require "utils"
|
||||
local normalize_affiliations = from_utils.normalize_affiliations
|
||||
local normalize_authors = from_utils.normalize_authors
|
||||
local normalize_latex_authors = from_utils.normalize_latex_authors
|
||||
|
||||
local from_authors = require "from_author_info_blocks"
|
||||
local default_marks = from_authors.default_marks
|
||||
local create_equal_contributors_block = from_authors.create_equal_contributors_block
|
||||
local create_affiliations_blocks = from_authors.create_affiliations_blocks
|
||||
local create_correspondence_blocks = from_authors.create_correspondence_blocks
|
||||
local is_corresponding_author = from_authors.is_corresponding_author
|
||||
local author_inline_generator = from_authors.author_inline_generator
|
||||
local create_authors_inlines = from_authors.create_authors_inlines
|
||||
-- [/import]
|
||||
|
||||
-- This is the main-part
|
||||
function Pandoc(doc)
|
||||
local meta = doc.meta
|
||||
local body = List:new{}
|
||||
|
||||
-- Support both `authors:` and `author:` YAML keys; skip if no valid author list
|
||||
local authors = meta.authors or meta.author
|
||||
if authors == nil or authors[1] == nil or authors[1].name == nil then
|
||||
return doc
|
||||
end
|
||||
meta.authors = List:new(authors)
|
||||
|
||||
local mark = function (mark_name) return default_marks[mark_name] end
|
||||
|
||||
body:extend(create_equal_contributors_block(meta.authors, mark) or {})
|
||||
body:extend(create_affiliations_blocks(meta.affiliations) or {})
|
||||
body:extend(create_correspondence_blocks(meta.authors, mark) or {})
|
||||
body:extend(doc.blocks)
|
||||
|
||||
for _i, author in ipairs(meta.authors) do
|
||||
author.test = is_corresponding_author(author)
|
||||
end
|
||||
|
||||
meta.affiliations = normalize_affiliations(meta.affiliations)
|
||||
meta.author = meta.authors:map(normalize_authors(meta.affiliations))
|
||||
|
||||
-- Overwrite authors with formatted values. We use a single, formatted
|
||||
-- string for most formats. LaTeX output, however, looks nicer if we
|
||||
-- provide a authors as a list.
|
||||
meta.author = pandoc.MetaInlines(create_authors_inlines(meta.author, mark))
|
||||
-- Institute info is now baked into the affiliations block.
|
||||
meta.affiliations = nil
|
||||
|
||||
return pandoc.Pandoc(body, meta)
|
||||
end
|
||||
@@ -0,0 +1,201 @@
|
||||
-- https://github.com/pandoc/lua-filters/commit/ca72210b453cc0d045360e0ae36448d019d7dfbf
|
||||
--[[
|
||||
affiliation-blocks – generate title components
|
||||
|
||||
Copyright © 2017–2021 Albert Krewinkel
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
]]
|
||||
|
||||
-- from @kapsner
|
||||
-- [import]
|
||||
local from_utils = require "utils"
|
||||
local has_key = from_utils.has_key
|
||||
-- [/import]
|
||||
local M = {}
|
||||
|
||||
-- taken from https://github.com/pandoc/lua-filters/blob/1660794b991c3553968beb993f5aabb99b317584/author-info-blocks/author-info-blocks.lua
|
||||
local List = require 'pandoc.List'
|
||||
local utils = require 'pandoc.utils'
|
||||
local stringify = utils.stringify
|
||||
|
||||
-- taken from https://github.com/pandoc/lua-filters/blob/1660794b991c3553968beb993f5aabb99b317584/author-info-blocks/author-info-blocks.lua
|
||||
local default_marks
|
||||
local default_marks = {
|
||||
corresponding_author = FORMAT == 'latex'
|
||||
and {pandoc.RawInline('latex', '*')}
|
||||
or {pandoc.Str '✉'},
|
||||
equal_contributor = FORMAT == 'latex'
|
||||
and {pandoc.RawInline('latex', '$\\dagger{}$')}
|
||||
or {pandoc.Str '*'},
|
||||
}
|
||||
M.default_marks = default_marks
|
||||
|
||||
-- modified by @kapsner
|
||||
-- taken from https://github.com/pandoc/lua-filters/blob/1660794b991c3553968beb993f5aabb99b317584/author-info-blocks/author-info-blocks.lua
|
||||
local function is_equal_contributor(author)
|
||||
if has_key(author, "attributes") then
|
||||
return author.attributes["equal-contributor"]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- taken from https://github.com/pandoc/lua-filters/blob/1660794b991c3553968beb993f5aabb99b317584/author-info-blocks/author-info-blocks.lua
|
||||
--- Create equal contributors note.
|
||||
local function create_equal_contributors_block(authors, mark)
|
||||
local has_equal_contribs = List:new(authors):find_if(is_equal_contributor)
|
||||
if not has_equal_contribs then
|
||||
return nil
|
||||
end
|
||||
local contributors = {
|
||||
pandoc.Superscript(mark'equal_contributor'),
|
||||
pandoc.Space(),
|
||||
pandoc.Str 'These authors contributed equally to this work.'
|
||||
}
|
||||
return List:new{pandoc.Para(contributors)}
|
||||
end
|
||||
M.create_equal_contributors_block = create_equal_contributors_block
|
||||
|
||||
|
||||
-- taken from https://github.com/pandoc/lua-filters/blob/1660794b991c3553968beb993f5aabb99b317584/author-info-blocks/author-info-blocks.lua
|
||||
local function intercalate(lists, elem)
|
||||
local result = List:new{}
|
||||
for i = 1, (#lists - 1) do
|
||||
result:extend(lists[i])
|
||||
result:extend(elem)
|
||||
end
|
||||
if #lists > 0 then
|
||||
result:extend(lists[#lists])
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- taken from https://github.com/pandoc/lua-filters/blob/1660794b991c3553968beb993f5aabb99b317584/author-info-blocks/author-info-blocks.lua
|
||||
--- Check whether the given author is a corresponding author
|
||||
local function is_corresponding_author(author)
|
||||
if has_key(author, "attributes") then
|
||||
if author.attributes["corresponding"] then
|
||||
return author.email
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
M.is_corresponding_author = is_corresponding_author
|
||||
|
||||
-- taken from https://github.com/pandoc/lua-filters/blob/1660794b991c3553968beb993f5aabb99b317584/author-info-blocks/author-info-blocks.lua
|
||||
--- Generate a block element containing the correspondence information
|
||||
local function create_correspondence_blocks(authors, mark)
|
||||
local corresponding_authors = List:new{}
|
||||
for _, author in ipairs(authors) do
|
||||
if is_corresponding_author(author) then
|
||||
local mailto = 'mailto:' .. utils.stringify(author.email)
|
||||
local author_with_mail = List:new(
|
||||
-- modified by @kapsner
|
||||
author.name.literal .. List:new{pandoc.Space(), pandoc.Str '<'} ..
|
||||
author.email .. List:new{pandoc.Str '>'}
|
||||
)
|
||||
local link = pandoc.Link(author_with_mail, mailto)
|
||||
table.insert(corresponding_authors, {link})
|
||||
end
|
||||
end
|
||||
if #corresponding_authors == 0 then
|
||||
return nil
|
||||
end
|
||||
local correspondence = List:new{
|
||||
pandoc.Superscript(mark'corresponding_author'),
|
||||
pandoc.Space(),
|
||||
pandoc.Str'Correspondence:',
|
||||
pandoc.Space()
|
||||
}
|
||||
local sep = List:new{pandoc.Str',', pandoc.Space()}
|
||||
return {
|
||||
pandoc.Para(correspondence .. intercalate(corresponding_authors, sep))
|
||||
}
|
||||
end
|
||||
M.create_correspondence_blocks = create_correspondence_blocks
|
||||
|
||||
-- taken from https://github.com/pandoc/lua-filters/blob/1660794b991c3553968beb993f5aabb99b317584/author-info-blocks/author-info-blocks.lua
|
||||
--- Create inlines for a single author (includes all author notes)
|
||||
local function author_inline_generator (get_mark)
|
||||
return function (author)
|
||||
local author_marks = List:new{}
|
||||
-- modified by @kapsner
|
||||
if has_key(author, "attributes") then
|
||||
if author.attributes["equal-contributor"] then
|
||||
author_marks[#author_marks + 1] = get_mark 'equal_contributor'
|
||||
end
|
||||
end
|
||||
local idx_str
|
||||
for _, idx in ipairs(author.affiliations) do
|
||||
if type(idx) ~= 'table' then
|
||||
idx_str = tostring(idx)
|
||||
else
|
||||
idx_str = stringify(idx)
|
||||
end
|
||||
author_marks[#author_marks + 1] = {pandoc.Str(idx_str)}
|
||||
end
|
||||
if is_corresponding_author(author) then
|
||||
author_marks[#author_marks + 1] = get_mark 'corresponding_author'
|
||||
end
|
||||
-- modified by @kapsner
|
||||
if FORMAT:match 'latex' then
|
||||
author.name.literal[#author.name.literal + 1] = pandoc.Superscript(intercalate(author_marks, {pandoc.Str ','}))
|
||||
return author
|
||||
else
|
||||
local res = List.clone(author.name.literal)
|
||||
res[#res + 1] = pandoc.Superscript(intercalate(author_marks, {pandoc.Str ','}))
|
||||
return res
|
||||
end
|
||||
end
|
||||
end
|
||||
M.author_inline_generator = author_inline_generator
|
||||
|
||||
-- taken from https://github.com/pandoc/lua-filters/blob/1660794b991c3553968beb993f5aabb99b317584/author-info-blocks/author-info-blocks.lua
|
||||
--- Generate a list of inlines containing all authors.
|
||||
local function create_authors_inlines(authors, mark)
|
||||
local inlines_generator = author_inline_generator(mark)
|
||||
local inlines = List:new(authors):map(inlines_generator)
|
||||
local and_str = List:new{pandoc.Space(), pandoc.Str'and', pandoc.Space()}
|
||||
|
||||
local last_author = inlines[#inlines]
|
||||
inlines[#inlines] = nil
|
||||
local result = intercalate(inlines, {pandoc.Str ',', pandoc.Space()})
|
||||
if #authors > 1 then
|
||||
if #authors == 2 then
|
||||
result:extend(and_str)
|
||||
else
|
||||
result:extend(List:new{pandoc.Str ","} .. and_str)
|
||||
end
|
||||
end
|
||||
result:extend(last_author)
|
||||
return result
|
||||
end
|
||||
M.create_authors_inlines = create_authors_inlines
|
||||
|
||||
-- taken from https://github.com/pandoc/lua-filters/blob/1660794b991c3553968beb993f5aabb99b317584/author-info-blocks/author-info-blocks.lua
|
||||
--- Generate a block list all affiliations, marked with arabic numbers.
|
||||
local function create_affiliations_blocks(affiliations)
|
||||
local affil_lines = List:new(affiliations):map(
|
||||
function (affil, i)
|
||||
local num_inlines = List:new{
|
||||
pandoc.Superscript{pandoc.Str(affil.number)},
|
||||
pandoc.Space()
|
||||
}
|
||||
return num_inlines .. affil.name
|
||||
end
|
||||
)
|
||||
return {pandoc.Para(intercalate(affil_lines, {pandoc.LineBreak()}))}
|
||||
end
|
||||
M.create_affiliations_blocks = create_affiliations_blocks
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,59 @@
|
||||
--[[
|
||||
ScholarlyMeta – normalize author/affiliation meta variables
|
||||
|
||||
Copyright (c) 2017-2021 Albert Krewinkel, Robert Winkler
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
]]
|
||||
|
||||
local List = require 'pandoc.List'
|
||||
local utils = require 'pandoc.utils'
|
||||
local stringify = utils.stringify
|
||||
|
||||
local M = {}
|
||||
|
||||
|
||||
-- taken from https://github.com/pandoc/lua-filters/blob/1660794b991c3553968beb993f5aabb99b317584/scholarly-metadata/scholarly-metadata.lua
|
||||
--- Returns a function which checks whether an object has the given ID.
|
||||
local function has_id(id)
|
||||
return function(x) return x.id == id end
|
||||
end
|
||||
|
||||
|
||||
-- taken from https://github.com/pandoc/lua-filters/blob/1660794b991c3553968beb993f5aabb99b317584/scholarly-metadata/scholarly-metadata.lua
|
||||
--- Resolve institute placeholders to full named objects
|
||||
local function resolve_institutes(institute, known_institutes)
|
||||
local unresolved_institutes
|
||||
if institute == nil then
|
||||
unresolved_institutes = {}
|
||||
elseif type(institute) == "string" or type(institute) == "number" then
|
||||
unresolved_institutes = {institute}
|
||||
else
|
||||
unresolved_institutes = institute
|
||||
end
|
||||
|
||||
local result = List:new{}
|
||||
for i, inst in ipairs(unresolved_institutes) do
|
||||
-- this has been modified by @kapsner
|
||||
--result[i] =
|
||||
-- known_institutes[tonumber(inst)] or
|
||||
-- known_institutes:find_if(has_id(pandoc.utils.stringify(inst))) or
|
||||
-- to_named_object(inst)
|
||||
intermed_val = known_institutes:find_if(has_id(stringify(inst)))
|
||||
result[i] = pandoc.MetaString(stringify(intermed_val.index))
|
||||
end
|
||||
return result
|
||||
end
|
||||
M.resolve_institutes = resolve_institutes
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,62 @@
|
||||
--[[
|
||||
authors-block – affiliations block extension for quarto
|
||||
|
||||
Copyright (c) 2023 Lorenz A. Kapsner
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
]]
|
||||
|
||||
local List = require 'pandoc.List'
|
||||
local utils = require 'pandoc.utils'
|
||||
local stringify = utils.stringify
|
||||
|
||||
-- [import]
|
||||
local from_scholarly = require "from_scholarly_metadata"
|
||||
local resolve_institutes = from_scholarly.resolve_institutes
|
||||
-- [/import]
|
||||
|
||||
local M = {}
|
||||
|
||||
-- from @kapsner
|
||||
local function normalize_affiliations(affiliations)
|
||||
local affiliations_norm = List:new(affiliations):map(
|
||||
function(affil, i)
|
||||
affil.index = pandoc.MetaInlines(pandoc.Str(tostring(i)))
|
||||
affil.id = pandoc.MetaString(stringify(affil.id))
|
||||
return affil
|
||||
end
|
||||
)
|
||||
return affiliations_norm
|
||||
end
|
||||
M.normalize_affiliations = normalize_affiliations
|
||||
|
||||
-- from https://stackoverflow.com/a/2282547
|
||||
local function has_key(set, key)
|
||||
return set[key] ~= nil
|
||||
end
|
||||
M.has_key = has_key
|
||||
|
||||
-- from @kapsner
|
||||
local function normalize_authors(affiliations)
|
||||
return function(auth)
|
||||
auth.id = pandoc.MetaString(stringify(auth.name))
|
||||
auth.affiliations = resolve_institutes(
|
||||
auth.affiliations,
|
||||
affiliations
|
||||
)
|
||||
return auth
|
||||
end
|
||||
end
|
||||
M.normalize_authors = normalize_authors
|
||||
|
||||
return M
|
||||
Binary file not shown.
@@ -0,0 +1,19 @@
|
||||
@article{knuth84,
|
||||
author = {Knuth, Donald E.},
|
||||
title = {Literate Programming},
|
||||
year = {1984},
|
||||
issue_date = {May 1984},
|
||||
publisher = {Oxford University Press, Inc.},
|
||||
address = {USA},
|
||||
volume = {27},
|
||||
number = {2},
|
||||
issn = {0010-4620},
|
||||
url = {https://doi.org/10.1093/comjnl/27.2.97},
|
||||
doi = {10.1093/comjnl/27.2.97},
|
||||
journal = {Comput. J.},
|
||||
month = may,
|
||||
pages = {97–111},
|
||||
numpages = {15}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
split_titlepage.py
|
||||
|
||||
Splits a Quarto-manuscript-rendered .docx into two files:
|
||||
- <output>-titlepage.docx : title, authors, affiliations (front matter)
|
||||
- <output>-body.docx : everything from the first section break onward
|
||||
|
||||
Why this works:
|
||||
Quarto's manuscript/scholarly docx title-block template emits a section
|
||||
break (<w:sectPr> inside a paragraph's <w:pPr>) immediately after the
|
||||
title-block content, before the body begins. This script finds that first
|
||||
section break in word/document.xml and splits there.
|
||||
|
||||
Safety check:
|
||||
Before writing output, the script verifies the "title page" portion
|
||||
actually contains the document's title and at least one author name
|
||||
(read from the source .qmd YAML front matter, or passed explicitly).
|
||||
If this check fails, the script aborts with a clear error rather than
|
||||
silently producing a wrong split - getting this wrong has real
|
||||
deanonymization consequences for blind peer review.
|
||||
|
||||
This script is self-contained (standard library only: zipfile, re, shutil)
|
||||
and does NOT depend on any external docx-editing toolkit. It works directly
|
||||
with the docx ZIP container, replacing only word/document.xml in each of
|
||||
two copies of the original archive.
|
||||
|
||||
Usage:
|
||||
python split_titlepage.py INPUT.docx OUTDIR \
|
||||
--title "Title" --author "Jane Doe" [--author "John Q. Doe" ...]
|
||||
|
||||
Exit codes:
|
||||
0 success
|
||||
1 split point not found
|
||||
2 safety check failed (title/author not found in detected title page)
|
||||
3 other error (bad args, file not found)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SECTPR_PATTERN = re.compile(
|
||||
r'<w:p\b[^>]*>(?:(?!</w:p>).)*?<w:sectPr\b.*?</w:sectPr>.*?</w:p>',
|
||||
re.DOTALL,
|
||||
)
|
||||
|
||||
# A bare sectPr also legitimately appears as the LAST element of body,
|
||||
# as a direct child of <w:body> (not inside a paragraph) -- that one
|
||||
# describes the final/only section and is NOT a split point on its own.
|
||||
# We only want sectPr that appears INSIDE a paragraph's pPr, which marks
|
||||
# an explicit section break before the end of the document.
|
||||
|
||||
|
||||
def find_first_section_break(document_xml: str) -> "tuple[int, int] | None":
|
||||
"""
|
||||
Returns (start, end) character offsets of the first paragraph
|
||||
containing a section break, or None if not found.
|
||||
"""
|
||||
match = SECTPR_PATTERN.search(document_xml)
|
||||
if not match:
|
||||
return None
|
||||
return match.span()
|
||||
|
||||
|
||||
def strip_tags_for_text_check(xml_fragment: str) -> str:
|
||||
"""Crude tag stripping for a plain-text containment check."""
|
||||
text = re.sub(r'<[^>]+>', ' ', xml_fragment)
|
||||
text = re.sub(r'\s+', ' ', text).strip()
|
||||
return text
|
||||
|
||||
|
||||
def read_document_xml(docx_path: Path) -> str:
|
||||
with zipfile.ZipFile(docx_path, "r") as z:
|
||||
return z.read("word/document.xml").decode("utf-8")
|
||||
|
||||
|
||||
def write_docx_with_replaced_document_xml(
|
||||
source_docx: Path, new_document_xml: str, dest_docx: Path
|
||||
) -> None:
|
||||
"""Copy source_docx to dest_docx, replacing only word/document.xml."""
|
||||
with zipfile.ZipFile(source_docx, "r") as src:
|
||||
with zipfile.ZipFile(dest_docx, "w", zipfile.ZIP_DEFLATED) as dst:
|
||||
for item in src.infolist():
|
||||
data = src.read(item.filename)
|
||||
if item.filename == "word/document.xml":
|
||||
data = new_document_xml.encode("utf-8")
|
||||
dst.writestr(item, data)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("input_docx", type=Path, help="Rendered anonymized .docx")
|
||||
parser.add_argument("outdir", type=Path, help="Directory to write split output into")
|
||||
parser.add_argument("--title", required=True, help="Document title, for the safety check")
|
||||
parser.add_argument(
|
||||
"--author", action="append", default=[],
|
||||
help="Author name for the safety check; repeat flag for multiple authors"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--authors", default=None,
|
||||
help="Comma-separated author names for the safety check "
|
||||
"(simpler alternative to repeating --author from a Taskfile)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--basename", default=None,
|
||||
help="Base name for output files (default: input filename stem)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--allow-no-author-match", action="store_true",
|
||||
help="Downgrade the author-match safety check from fatal to a warning"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
authors = list(args.author)
|
||||
if args.authors:
|
||||
authors.extend(a.strip() for a in args.authors.split(",") if a.strip())
|
||||
args.author = authors
|
||||
|
||||
if not args.input_docx.exists():
|
||||
print(f"ERROR: input file not found: {args.input_docx}", file=sys.stderr)
|
||||
sys.exit(3)
|
||||
|
||||
basename = args.basename or args.input_docx.stem
|
||||
args.outdir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
print(f"Splitting title page from: {args.input_docx}")
|
||||
|
||||
try:
|
||||
xml = read_document_xml(args.input_docx)
|
||||
except KeyError:
|
||||
print("ERROR: word/document.xml not found inside the docx - is this a valid .docx?",
|
||||
file=sys.stderr)
|
||||
sys.exit(3)
|
||||
|
||||
body_match = re.search(r"<w:body\b[^>]*>(.*)</w:body>", xml, re.DOTALL)
|
||||
if not body_match:
|
||||
print("ERROR: could not locate <w:body> in document.xml", file=sys.stderr)
|
||||
sys.exit(3)
|
||||
body_start, body_end = body_match.span(1)
|
||||
body_content = xml[body_start:body_end]
|
||||
|
||||
split = find_first_section_break(body_content)
|
||||
if split is None:
|
||||
print(
|
||||
"ERROR: no section break found in document body. "
|
||||
"Expected Quarto's manuscript docx template to emit a "
|
||||
"section break after the title block. Aborting split — "
|
||||
"check that the source document actually came from the "
|
||||
"manuscript title-block template.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
_, split_end = split
|
||||
titlepage_fragment = body_content[:split_end]
|
||||
body_fragment = body_content[split_end:]
|
||||
|
||||
# --- Safety check ---
|
||||
titlepage_text = strip_tags_for_text_check(titlepage_fragment)
|
||||
title_found = args.title.strip() != "" and args.title.strip() in titlepage_text
|
||||
authors_found = [a for a in args.author if a.strip() and a.strip() in titlepage_text]
|
||||
|
||||
print(f" Detected title page text (first 200 chars): {titlepage_text[:200]!r}")
|
||||
|
||||
problems = []
|
||||
if not title_found:
|
||||
problems.append(f"title {args.title!r} not found in detected title-page text")
|
||||
if args.author and not authors_found:
|
||||
problems.append(
|
||||
f"none of the expected authors {args.author!r} found in detected title-page text"
|
||||
)
|
||||
|
||||
if problems:
|
||||
msg = (
|
||||
"SAFETY CHECK FAILED: the detected 'title page' section does not "
|
||||
"appear to contain the expected title/author metadata:\n - "
|
||||
+ "\n - ".join(problems)
|
||||
+ "\nThis usually means the section-break detection found the "
|
||||
"wrong split point. Refusing to write output to avoid a silent "
|
||||
"deanonymization risk."
|
||||
)
|
||||
if args.allow_no_author_match:
|
||||
print(f"WARNING: {msg}\n(Continuing anyway because --allow-no-author-match was set.)",
|
||||
file=sys.stderr)
|
||||
else:
|
||||
print(f"ERROR: {msg}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
else:
|
||||
print(f" Safety check passed (title found: {title_found}, authors found: {authors_found})")
|
||||
|
||||
def build_full_xml(body_inner: str) -> str:
|
||||
return xml[:body_start] + body_inner + xml[body_end:]
|
||||
|
||||
titlepage_out = args.outdir / f"{basename}-titlepage.docx"
|
||||
body_out = args.outdir / f"{basename}-body.docx"
|
||||
|
||||
write_docx_with_replaced_document_xml(
|
||||
args.input_docx, build_full_xml(titlepage_fragment), titlepage_out
|
||||
)
|
||||
write_docx_with_replaced_document_xml(
|
||||
args.input_docx, build_full_xml(body_fragment), body_out
|
||||
)
|
||||
|
||||
print(f"Done.\n Title page -> {titlepage_out}\n Body -> {body_out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
task_utils.py — cross-platform file operations for the Taskfile pipeline.
|
||||
|
||||
Replaces shell-specific commands (PowerShell Compress-Archive, Remove-Item,
|
||||
etc.) with plain Python stdlib calls that behave identically on Windows,
|
||||
Linux, and macOS. Called from Task as:
|
||||
|
||||
python ../resources/scripts/task_utils.py <subcommand> [args...]
|
||||
|
||||
Subcommands:
|
||||
zip SRC_DIR DEST_ZIP
|
||||
Zip the contents of SRC_DIR into DEST_ZIP (overwrites if it exists).
|
||||
Fails with a clear message (exit 1) if SRC_DIR doesn't exist.
|
||||
|
||||
zip-if-exists SRC_DIR DEST_ZIP
|
||||
Same as `zip`, but exits 0 with a warning (no error) if SRC_DIR
|
||||
doesn't exist, instead of failing. Used for optional things like
|
||||
a project's data/ folder.
|
||||
|
||||
clean-project PROJECT_DIR
|
||||
Remove _output/, .quarto/, and any *_files/*_cache directories
|
||||
found anywhere under PROJECT_DIR. Safe to call even if nothing
|
||||
exists yet.
|
||||
|
||||
copy-if-exists SRC DEST
|
||||
Copy a single file from SRC to DEST if SRC exists; otherwise
|
||||
print a warning and exit 0 (does not fail the pipeline).
|
||||
|
||||
today
|
||||
Print today's date as YYYY-MM-DD (used for the finalized/ folder
|
||||
name). No platform-specific date command needed.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
import sys
|
||||
import zipfile
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def cmd_zip(src_dir: str, dest_zip: str, allow_missing: bool) -> int:
|
||||
src = Path(src_dir)
|
||||
dest = Path(dest_zip)
|
||||
|
||||
if not src.exists() or not src.is_dir():
|
||||
msg = f"'{src}' does not exist or is not a directory"
|
||||
if allow_missing:
|
||||
print(f"WARNING: {msg} — skipping zip of {dest}")
|
||||
return 0
|
||||
print(f"ERROR: {msg} — did you render first?", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
files = [p for p in src.rglob("*") if p.is_file()]
|
||||
if not files:
|
||||
msg = f"'{src}' exists but contains no files"
|
||||
if allow_missing:
|
||||
print(f"WARNING: {msg} — skipping zip of {dest}")
|
||||
return 0
|
||||
print(f"ERROR: {msg}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
if dest.exists():
|
||||
dest.unlink()
|
||||
|
||||
with zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||
for f in files:
|
||||
zf.write(f, f.relative_to(src))
|
||||
|
||||
print(f"Created {dest} ({len(files)} files)")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_clean_project(project_dir: str) -> int:
|
||||
root = Path(project_dir)
|
||||
|
||||
for name in ("_output", ".quarto"):
|
||||
target = root / name
|
||||
if target.exists():
|
||||
shutil.rmtree(target, ignore_errors=True)
|
||||
print(f"Removed {target}")
|
||||
|
||||
for pattern in ("*_files", "*_cache"):
|
||||
for match in root.rglob(pattern):
|
||||
if match.is_dir():
|
||||
shutil.rmtree(match, ignore_errors=True)
|
||||
print(f"Removed {match}")
|
||||
|
||||
return 0
|
||||
|
||||
def cmd_clean_zips(project_dir: str) -> int:
|
||||
root = Path(project_dir)
|
||||
|
||||
for match in root.rglob("*.zip"):
|
||||
if match.is_file():
|
||||
shutil.rmtree(match, ignore_errors=True)
|
||||
print(f"Removed {match}")
|
||||
|
||||
return 0
|
||||
|
||||
def cmd_copy_if_exists(src: str, dest: str) -> int:
|
||||
src_path = Path(src)
|
||||
dest_path = Path(dest)
|
||||
|
||||
if not src_path.exists():
|
||||
print(f"WARNING: '{src_path}' not found, skipping copy")
|
||||
return 0
|
||||
|
||||
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(src_path, dest_path)
|
||||
print(f"Copied {src_path} -> {dest_path}")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_today() -> int:
|
||||
print(date.today().isoformat())
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
subcommand = sys.argv[1]
|
||||
args = sys.argv[2:]
|
||||
|
||||
if subcommand == "zip":
|
||||
if len(args) != 2:
|
||||
print("usage: task_utils.py zip SRC_DIR DEST_ZIP", file=sys.stderr)
|
||||
return 1
|
||||
return cmd_zip(args[0], args[1], allow_missing=False)
|
||||
|
||||
if subcommand == "zip-if-exists":
|
||||
if len(args) != 2:
|
||||
print("usage: task_utils.py zip-if-exists SRC_DIR DEST_ZIP", file=sys.stderr)
|
||||
return 1
|
||||
return cmd_zip(args[0], args[1], allow_missing=True)
|
||||
|
||||
if subcommand == "clean-project":
|
||||
if len(args) != 1:
|
||||
print("usage: task_utils.py clean-project PROJECT_DIR", file=sys.stderr)
|
||||
return 1
|
||||
return cmd_clean_project(args[0])
|
||||
|
||||
if subcommand == "clean-zips":
|
||||
if len(args) != 1:
|
||||
print("usage: task_utils.py clean-zips PROJECT_DIR", file=sys.stderr)
|
||||
return 1
|
||||
return cmd_clean_zips(args[0])
|
||||
|
||||
if subcommand == "copy-if-exists":
|
||||
if len(args) != 2:
|
||||
print("usage: task_utils.py copy-if-exists SRC DEST", file=sys.stderr)
|
||||
return 1
|
||||
return cmd_copy_if_exists(args[0], args[1])
|
||||
|
||||
if subcommand == "today":
|
||||
return cmd_today()
|
||||
|
||||
print(f"ERROR: unknown subcommand '{subcommand}'", file=sys.stderr)
|
||||
print(__doc__, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user