Skip to content
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
6 changes: 5 additions & 1 deletion docs/integration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,15 @@ very first version of this example)::
# Generate input file name
set(infile "${CMAKE_CURRENT_SOURCE_DIR}/${infileName}")

# Create the dependency file
set(depfile "${CMAKE_CURRENT_BINARY_DIR}/${outfileName}.d")

# Custom command to do the processing
add_custom_command(
OUTPUT "${outfile}"
COMMAND fypp "${infile}" "${outfile}"
COMMAND fypp "${infile}" "${outfile}" --depfile "${depfile}"
MAIN_DEPENDENCY "${infile}"
DEPFILE "${depfile}"
VERBATIM)

# Finally add output file to a list
Expand Down
88 changes: 80 additions & 8 deletions src/fypp.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,26 @@ def __init__(self, includedirs=None, encoding='utf-8'):
# Directory of current file
self._curdir = None

# All files that have been included
self._included_files = []


def get_dependencies(self):
'''Returns the list of files included during parsing.

Returns:
list of str: List of included file paths.
'''
return self._included_files


def parsefile(self, fobj):
'''Parses file or a file like object.

Args:
fobj (str or file): Name of a file or a file like object.
'''
self._included_files = []
if isinstance(fobj, pathlib.Path):
fobj = str(fobj)

Expand All @@ -259,6 +272,9 @@ def parsefile(self, fobj):


def _includefile(self, span, fobj, fname, curdir):
# Don't add the root file, only later includes
if self._curfile:
self._included_files.append(fname)
oldfile = self._curfile
olddir = self._curdir
self._curfile = fname
Expand All @@ -274,6 +290,7 @@ def parse(self, txt):
Args:
txt (str): Text to parse.
'''
self._included_files = []
self._curfile = STRING
self._curdir = ''
self._parse_txt(None, self._curfile, txt)
Expand Down Expand Up @@ -2420,6 +2437,31 @@ def process_text(self, txt):
return self._render()


def get_dependencies(self):
'''Returns the list of included file dependencies.

Returns:
list of str: List of included file paths.
'''
return self._parser.get_dependencies()


def write_dependencies(self, outfile, depfile):
'''Writes a Make-compatible dependency file.

Args:
outfile (str): Name of the output file (the target in the dep rule).
depfile (str): Path where the dependency file should be written.
'''
def quote(text):
# Normalize to forward slashes for Make compatibility
text = text.replace('\\', '/')
return text.replace('$', '$$').replace(' ', '\\ ').replace('#', '\\#')
dependencies = [quote(d) for d in self.get_dependencies()]
with open(depfile, 'w', encoding='utf-8') as fobj:
fobj.write('{}: {}'.format(quote(outfile), ' '.join(dependencies)))


def _render(self):
output = self._renderer.render(self._builder.tree)
self._builder.reset()
Expand Down Expand Up @@ -2555,6 +2597,7 @@ def __init__(self, options=None, evaluator_factory=Evaluator,
else:
raise FyppFatalError('renderer_factory has incorrect signature')
self._preprocessor = Processor(parser, builder, renderer)
self._depfile = getattr(options, 'depfile', None)


def process_file(self, infile, outfile=None):
Expand All @@ -2566,7 +2609,6 @@ def process_file(self, infile, outfile=None):
outfile (str, optional): Name of the file to write the result to.
If its value is '-', result is written to stdout. If not
present, result will be returned as string.
env (dict, optional): Additional definitions for the evaluator.

Returns:
str: Result of processed input, if no outfile was specified.
Expand All @@ -2576,13 +2618,15 @@ def process_file(self, infile, outfile=None):
if outfile is None:
return output
if outfile == '-':
outfile = sys.stdout
outfile_handle = sys.stdout
else:
outfile = _open_output_file(outfile, self._encoding,
self._create_parent_folder)
outfile.write(output)
if outfile != sys.stdout:
outfile.close()
outfile_handle = _open_output_file(outfile, self._encoding,
self._create_parent_folder)
outfile_handle.write(output)
if outfile_handle != sys.stdout:
outfile_handle.close()
if self._depfile and outfile and outfile != '-':
self.write_dependencies(outfile, self._depfile)
return None


Expand All @@ -2591,14 +2635,32 @@ def process_text(self, txt):

Args:
txt (str): String to process.
env (dict, optional): Additional definitions for the evaluator.

Returns:
str: Processed content.
'''
return self._preprocessor.process_text(txt)


def get_dependencies(self):
'''Returns the list of included file dependencies.

Returns:
list of str: List of included file paths.
'''
return self._preprocessor.get_dependencies()


def write_dependencies(self, outfile, depfile):
'''Writes a Make-compatible dependency file.

Args:
outfile (str): Name of the output file (the target in the dep rule).
depfile (str): Path where the dependency file should be written.
'''
self._preprocessor.write_dependencies(outfile, depfile)


@staticmethod
def _apply_definitions(defines, evaluator, evaluate):
for define in defines:
Expand Down Expand Up @@ -2681,6 +2743,8 @@ class FyppOptions(optparse.Values):
setting.
create_parent_folder (bool): Whether the parent folder for the output
file should be created if it does not exist. Default: False.
depfile (str | None): If set, where to write a Makefile compatible
dependency file. Default: None.
'''

def __init__(self):
Expand All @@ -2703,6 +2767,7 @@ def __init__(self):
self.encoding = 'utf-8'
self.create_parent_folder = False
self.file_var_root = None
self.depfile = None


class FortranLineFolder:
Expand Down Expand Up @@ -2946,6 +3011,10 @@ def get_option_parser():
parser.add_option('--file-var-root', metavar='DIR', dest='file_var_root',
default=defs.file_var_root, help=msg)

msg = 'Write a Make-compatible dependency file to this location'
parser.add_option('--depfile', metavar='DEPFILE', dest='depfile',
default=defs.depfile, help=msg)

return parser


Expand All @@ -2956,6 +3025,9 @@ def run_fypp():
opts, leftover = optparser.parse_args(values=options)
infile = leftover[0] if len(leftover) > 0 else '-'
outfile = leftover[1] if len(leftover) > 1 else '-'
if outfile == '-' and opts.depfile:
raise optparse.OptionValueError(
"--depfile cannot be used when writing to stdout")
try:
tool = Fypp(opts)
tool.process_file(infile, outfile)
Expand Down
1 change: 1 addition & 0 deletions test/include/escaped_includes.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#:include 'need$ #escape.inc'
2 changes: 2 additions & 0 deletions test/include/multi_includes.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#:include 'fypp1.inc'
#:include 'fypp2.inc'
1 change: 1 addition & 0 deletions test/include/subfolder/need$ #escape.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#:include 'fypp2.inc'
5 changes: 0 additions & 5 deletions test/runtests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ else
pythons="python3"
fi
root=".."
if [ -z "$PYTHONPATH" ]; then
export PYTHONPATH="$root/src"
else
export PYTHONPATH="$root/src:$PYTHONPATH"
fi
cd $testdir
failed="0"
failing_pythons=""
Expand Down
63 changes: 63 additions & 0 deletions test/test_fypp.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
'''Unit tests for testing Fypp.'''
from pathlib import Path
import os
import platform
import sys
import tempfile
import unittest

# Allow for importing fypp
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))

import fypp


Expand Down Expand Up @@ -3056,6 +3063,27 @@ def _importmodule(module):
]


DEPFILE_TESTS = [
('basic',
([_incdir('include')],
'include/subfolder/include_fypp1.inc',
'{output}: include/fypp1.inc',
)
),
('multiple_includes',
([_incdir('include/subfolder')],
'include/multi_includes.inc',
'{output}: include/fypp1.inc include/subfolder/fypp2.inc',
)
),
('escapes',
([_incdir('include'), _incdir('include/subfolder')],
'include/escaped_includes.inc',
'{output}: include/subfolder/need$$\\ \\#escape.inc include/subfolder/fypp2.inc',
)
),
]

def _get_test_output_method(args, inp, out):
'''Returns a test method for checking correctness of Fypp output.

Expand Down Expand Up @@ -3102,6 +3130,33 @@ def test_output_from_file_input(self):
return test_output_from_file_input


def _get_test_depfile_method(args, inputfile, expected):
'''Returns a test method for checking correctness of depfile.

Args:
args (list of str): Command-line arguments to pass to Fypp.
inputfile (str): Input file with Fypp directives.
expected (str): Expected depfile content (with {output} placeholder).

Returns:
method: Method to test equality of depfile with result delivered by Fypp.
'''

def test_depfile(self):
'''Tests whether Fypp result matches expected output when input is in a file.'''
output = self._get_tempfile()
depfile = self._get_tempfile()
optparser = fypp.get_option_parser()
options, leftover = optparser.parse_args(args + ['--depfile', depfile])
self.assertEqual(len(leftover), 0)
tool = fypp.Fypp(options)
tool.process_file(inputfile, output)
with open(depfile, 'r', encoding='utf-8') as f:
got = f.read().strip()
self.assertEqual(got, expected.format(output=output.replace('\\', '/')))
return test_depfile



def _get_test_exception_method(args, inp, exceptions):
'''Returns a test method for checking correctness of thrown exception.
Expand Down Expand Up @@ -3199,6 +3254,14 @@ class ExceptionTest(_TestContainer): pass
class ImportTest(_TestContainer): pass
ImportTest.add_test_methods(IMPORT_TESTS, _get_test_output_method)

class DepfileTest(_TestContainer):
def _get_tempfile(self):
_fd, output = tempfile.mkstemp()
os.close(_fd)
self.addCleanup(os.unlink, output)
return output
DepfileTest.add_test_methods(DEPFILE_TESTS, _get_test_depfile_method)


if __name__ == '__main__':
unittest.main()