Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds two new options + several fixes #8

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 26 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,50 @@
flatex.py
---------

This "flattens" a LaTeX document by replacing all \input{X} lines w/ the text actually contained in X.
I recently re-factored it to use the `click` module to create a command line interface.
This "flattens" a LaTeX document by replacing all `\input{X}` lines with the text actually contained in X. It uses the `click` module to create a command line interface.

Copyright, 2015, John J. Horton (john.joseph.horton@gmail.com)
Distributed under the terms of the GNU General Public License
See http://www.gnu.org/licenses/gpl.txt for details.

It was inspired by:
http://tex.stackexchange.com/questions/21838/replace-inputfilex-by-the-content-of-filex-automatically/21840#21840
Two new options are available:
- Specify a .tex subfolder for input files similarly to what the `input@path` command does in the main file
- Launch an interactive session to allow the user to select only specific sections to import

There are C and perl versions of this, but I wanted a pure python version to fit in w/ my existing paper-creating toolchain.
It was inspired by [this discussion on Stack Overflow](http://tex.stackexchange.com/questions/21838/replace-inputfilex-by-the-content-of-filex-automatically/21840#21840). There are C and perl versions of this, but I wanted a pure python version to fit in with my existing paper-creating toolchain.

To install
----------
git clone git@github.com:johnjosephhorton/flatex.git
git clone https://github.com/johnjosephhorton/flatex.git
cd flatex
pip install --editable .

To use as a stand-alone script
Usage
-----------------------------
As a stand-alone script:

flatex inputfile.tex outputfile.tex

If you want to include the bbl file as well
--------------------------------------------
If you want to include the bbl file as well:

flatex --include_bbl inputfile.tex outputfile.tex

If you want to specify a .tex subfolder:

Limitations:
------------
flatex inputfile.tex outputfile.tex --tex_folder folder_name

If you want to select only specific input files to expand:

1) It doesn't do \includes - just inputs.
flatex inputfile.tex outputfile.tex --interactive

2) I haven't tested it for nested inputs (thought it's designed
to work on files like).

3) I haven't tested it for more complicated file arrangements
e.g., realtive reference inputs that are more complex that just
a file living in the same directory.

4) The test case writes to the /tmp folder - so the test probably
Limitations:
------------

1. It does also recognize `\includes` commands, but this has not been tested.
1. Tested for nested inputs, although not extensively (only two levels deep).
1. Tested for file living in subdirectories, only one level deep.
1. The test case writes to the /tmp folder - so the test probably
wouldn't work on Windows(?).


Copyright, 2015, John J. Horton (john.joseph.horton@gmail.com)
Distributed under the terms of the GNU General Public License
See http://www.gnu.org/licenses/gpl.txt for details.
126 changes: 86 additions & 40 deletions flatex.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import click
import os
import re
import sys
import click

def is_input(line):
def is_input(line, include_too=True):
"""
Determines whether or not a read in line contains an uncommented out
\input{} statement. Allows only spaces between start of line and
'\input{}'.
Determines via regex whether or not a line from file contains an
uncommented \\input{} statement. Allows only spaces between start
of line and '\\input{}'.
"""
#tex_input_re = r"""^\s*\\input{[^}]*}""" # input only
tex_input_re = r"""(^[^\%]*\\input{[^}]*})|(^[^\%]*\\include{[^}]*})""" # input or include
if include_too:
# input or include (not tested)
tex_input_re = r"""(^[^\%]*\\input{[^}]*})|(^[^\%]*\\include{[^}]*})"""
else:
# input only
tex_input_re = r"""^\s*\\input{[^}]*}"""
return re.search(tex_input_re, line)


Expand All @@ -19,44 +22,73 @@ def get_input(line):
Gets the file name from a line containing an input statement.
"""
tex_input_filename_re = r"""{[^}]*"""
m = re.search(tex_input_filename_re, line)
return m.group()[1:]
matched_text = re.search(tex_input_filename_re, line)
return matched_text.group()[1:]


def combine_path(base_path, relative_ref):
def combine_path(base_path, relative_ref, tex_folder):
"""
Combines the base path of the tex document being worked on with the
relate reference found in that document.

Use specific .tex folder if provided by the user.
"""
if (base_path != ""):
if base_path:
os.chdir(base_path)
# avoid duplicating tex_folder
if tex_folder and tex_folder in relative_ref:
tex_folder = ''
# Handle if .tex is supplied directly with file name or not
if relative_ref.endswith('.tex'):
return os.path.join(base_path, relative_ref)
return os.path.join(base_path, tex_folder, relative_ref)
else:
return os.path.abspath(relative_ref) + '.tex'
return os.path.abspath(os.path.join(tex_folder, relative_ref))+'.tex'


def expand_file(base_file, current_path, include_bbl, noline):
def expand_file(base_file, current_path, include_bbl, noline, tex_folder, interactive):
"""
Recursively-defined function that takes as input a file and returns it
with all the inputs replaced with the contents of the referenced file.
Recursively-defined function that:
- takes as input a file and opens it
- looks for 'input' or 'include' commands and get their file references
- analyze the references for further commands (recursively)
- then expands their content inside the main tex, appending it
- and return the complete file

With the interactive option the user is allowed to chose which file to
expand (if maybe some section are still worked on, or don't need to be added.)
"""
output_lines = []
f = open(base_file, "r")
for line in f:
if is_input(line):
new_base_file = combine_path(current_path, get_input(line))
output_lines += expand_file(new_base_file, current_path, include_bbl, noline)
if noline:
pass
with open(base_file, "r") as file_to_read:
for line in file_to_read:
bbl_flag = (
line.startswith("\\bibliography")
and (not line.startswith("\\bibliographystyle")))
if is_input(line):
input_str = get_input(line)
if interactive:
prompt_q = "Expand this section (Y or yes to proceed)?\n {}\n".format(input_str)
user_input = raw_input(prompt_q) # Python 3 incompatibility
user_input = user_input.lower()
proceed = True if (
user_input == 'y' or user_input == 'yes') else False
else:
proceed = True
if proceed:
new_base_file = combine_path(current_path, input_str, tex_folder)
output_lines += expand_file(
new_base_file, current_path, include_bbl,
noline, tex_folder, interactive)
# add a new line after each file input
if noline:
pass
else:
output_lines.append('\n')
else:
output_lines.append(line)
elif include_bbl and bbl_flag:
output_lines += bbl_file(base_file)
else:
output_lines.append('\n') # add a new line after each file input
elif include_bbl and line.startswith("\\bibliography") and (not line.startswith("\\bibliographystyle")):
output_lines += bbl_file(base_file)
else:
output_lines.append(line)
f.close()
output_lines.append(line)
return output_lines


Expand All @@ -69,19 +101,33 @@ def bbl_file(base_file):


@click.command()
@click.argument('base_file', type = click.Path())
@click.argument('output_file', type = click.Path())
@click.argument('base_file', type=click.Path())
@click.argument('output_file', type=click.Path())
@click.option('--include_bbl/--no_bbl', default=False)
@click.option("--noline", is_flag = True)
def main(base_file, output_file, include_bbl = False, noline = False):

@click.option('--noline', is_flag=True)
@click.option('--tex_folder', default='')
@click.option('--interactive', is_flag=True, default=False)
def main(
base_file, output_file, include_bbl=False,
noline=False, tex_folder='', interactive=False):
"""
This "flattens" a LaTeX document by replacing all \input{X} lines w/ the
This "flattens" a LaTeX document by replacing all \\input{X} lines w/ the
text actually contained in X. See associated README.md for details.

If .tex files are placed in a specific folder via input@path command in
the preamble of the latex file the tex_folder option must be specified.

Input:
base_file: (str) main .tex file to explore
output_file: (str) .tex file which will be produced as output
include_bbl: (bool) if True the bibliography is included
noline: (bool) avoid newline after merging files
tex_folder: (str) use specific .tex folder
"""
current_path = os.path.split(base_file)[0]
g = open(output_file, "w")
g.write(''.join(expand_file(base_file, current_path, include_bbl, noline)))
g.close()
with open(output_file, "w") as file_to_write:
file_to_write.write(''.join(
expand_file(
base_file, current_path, include_bbl,
noline, tex_folder, interactive)))
return None