[osmium-tool] 01/05: Imported Upstream version 1.6.0

Bas Couwenberg sebastic at debian.org
Tue Mar 7 18:20:11 UTC 2017


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository osmium-tool.

commit 646424f1a65a3cedd2f0b2fec857f8113fea5c95
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Tue Mar 7 18:53:04 2017 +0100

    Imported Upstream version 1.6.0
---
 CHANGELOG.md                                       |   41 +-
 CMakeLists.txt                                     |   10 +-
 cmake/FindOsmium.cmake                             |   14 +
 man/osmium-add-locations-to-ways.md                |    4 +
 man/osmium-apply-changes.md                        |   39 +-
 man/osmium-extract.md                              |   35 +-
 man/osmium-getid.md                                |   12 +-
 man/osmium-merge.md                                |    4 +
 man/osmium-tags-filter.md                          |  156 ++
 man/osmium.md                                      |    4 +
 src/CMakeLists.txt                                 |    2 +
 src/command_apply_changes.cpp                      |  160 +-
 src/command_apply_changes.hpp                      |    3 +
 src/command_cat.cpp                                |    2 +-
 src/command_check_refs.cpp                         |    2 +-
 src/command_extract.cpp                            |   52 +-
 src/command_extract.hpp                            |    1 +
 src/command_fileinfo.cpp                           |   34 +-
 src/command_getid.cpp                              |   42 +-
 src/command_getid.hpp                              |   10 +-
 src/command_merge.cpp                              |    1 +
 src/command_merge_changes.cpp                      |    1 +
 src/command_show.cpp                               |    4 +
 src/command_sort.cpp                               |    1 +
 src/command_tags_filter.cpp                        |  385 ++++
 src/{command_getid.hpp => command_tags_filter.hpp} |   47 +-
 src/command_time_filter.cpp                        |    2 +
 src/commands.cpp                                   |    5 +
 src/extract/extract.hpp                            |   11 +
 src/extract/geojson_file_parser.cpp                |   16 +-
 src/extract/strategy_complete_ways.cpp             |    9 +-
 .../strategy_complete_ways_with_history.cpp        |    2 +-
 src/extract/strategy_simple.cpp                    |    7 +-
 src/extract/strategy_smart.cpp                     |    9 +-
 src/io.cpp                                         |    2 +-
 src/util.cpp                                       |   43 +
 src/util.hpp                                       |    6 +
 test/CMakeLists.txt                                |    3 +
 test/apply-changes/CMakeLists.txt                  |    1 +
 test/apply-changes/input-data-low.osm              |   22 +
 test/apply-changes/output-data-low.osm             |   22 +
 test/extract/CMakeLists.txt                        |   24 +
 test/extract/input1.osm                            |   41 +
 test/extract/output-complete-ways.osm              |   30 +
 test/extract/output-simple.osm                     |   23 +
 test/extract/output-smart-nonmp.osm                |   30 +
 test/extract/output-smart.osm                      |   37 +
 test/extract/test_unit.cpp                         |   63 +-
 test/include/catch.hpp                             | 2261 +++++++++++++-------
 test/merge/CMakeLists.txt                          |    2 +-
 test/tags-filter/CMakeLists.txt                    |   30 +
 test/tags-filter/input.osm                         |   30 +
 test/tags-filter/output-amenity.osm                |    6 +
 test/tags-filter/output-highway-r.osm              |   19 +
 test/tags-filter/output-highway.osm                |   15 +
 test/tags-filter/output-no-note.osm                |   19 +
 test/tags-filter/output-note.osm                   |   14 +
 test/tags-filter/test_unit.cpp                     |   57 +
 test/util/test_unit.cpp                            |   11 +
 zsh_completion/_osmium                             |   33 +-
 60 files changed, 3022 insertions(+), 949 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf725e3..6959f9b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,44 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 ### Fixed
 
 
+## [1.6.0] - 2017-03-07
+
+### Added
+
+- New `tags-filter` command for filtering OSM files based on tag keys and
+  values.
+- Add option `--locations-on-ways` to `apply-changes` for updating files
+  created with `add-locations-to-ways`.
+- Add optional `output_header` on extracts in config file. (#47)
+- Add `--change-file-format` to `apply-changes` format for setting format
+  of change files.
+- Add `--set-bounds` option to `extract` command.
+
+### Changed
+
+- Now requires libosmium 2.12.
+- Deprecated `--history` option on `getid` command in favour of
+  `--with-history` for consistency with other commands.
+- Use new `RelationsMapIndex` from libosmium for `getid` instead of
+  `std::multimap`.
+- Update included version of Catch unit test framework to 1.8.1 which required
+  some changes in the tests.
+- Use `osmium::util::file_size` instead of our own `filesize()` function.
+- Miscellaneous code cleanups and improved warning messages and man pages.
+
+### Fixed
+
+- Add `-pthread` compiler and linker options on Linux/OSX. This should fix
+  a problem where some linker versions will not link binaries correctly when
+  the `--as-needed` option is used.
+- Typo in GeoJSON parser which broke MultiPolygon support.
+- Wrong description of -S option in extract man page.
+- Windows build problem related to forced build for old Windows versions.
+- All but the first polygon in a GeoJSON multipolygon were ignored by the
+  `extract` command.
+- Zsh command line completion for some commands.
+
+
 ## [1.5.1] - 2017-01-19
 
 ### Changed
@@ -213,7 +251,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 - Minor updates to documentation and build system
 
 
-[unreleased]: https://github.com/osmcode/osmium-tool/compare/v1.5.1...HEAD
+[unreleased]: https://github.com/osmcode/osmium-tool/compare/v1.6.0...HEAD
+[1.6.0]: https://github.com/osmcode/osmium-tool/compare/v1.5.1...v1.6.0
 [1.5.1]: https://github.com/osmcode/osmium-tool/compare/v1.5.0...v1.5.1
 [1.5.0]: https://github.com/osmcode/osmium-tool/compare/v1.4.1...v1.5.0
 [1.4.1]: https://github.com/osmcode/osmium-tool/compare/v1.4.0...v1.4.1
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f596dc0..e209dea 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,8 +25,8 @@ set(CMAKE_CONFIGURATION_TYPES "Debug;Release;RelWithDebInfo;MinSizeRel;Dev"
 project(osmium)
 
 set(OSMIUM_VERSION_MAJOR 1)
-set(OSMIUM_VERSION_MINOR 5)
-set(OSMIUM_VERSION_PATCH 1)
+set(OSMIUM_VERSION_MINOR 6)
+set(OSMIUM_VERSION_PATCH 0)
 
 set(OSMIUM_VERSION ${OSMIUM_VERSION_MAJOR}.${OSMIUM_VERSION_MINOR}.${OSMIUM_VERSION_PATCH})
 
@@ -44,7 +44,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
 find_package(Boost 1.55.0 REQUIRED COMPONENTS program_options)
 include_directories(SYSTEM ${Boost_INCLUDE_DIRS})
 
-find_package(Osmium 2.11.0 REQUIRED COMPONENTS io)
+find_package(Osmium 2.12.0 REQUIRED COMPONENTS io)
 include_directories(SYSTEM ${OSMIUM_INCLUDE_DIRS})
 
 
@@ -150,6 +150,7 @@ if(PANDOC)
     add_man_page(1 osmium-renumber)
     add_man_page(1 osmium-show)
     add_man_page(1 osmium-sort)
+    add_man_page(1 osmium-tags-filter)
     add_man_page(1 osmium-time-filter)
     add_man_page(5 osmium-file-formats)
 
@@ -191,8 +192,7 @@ else()
 endif()
 
 if(WIN32)
-    add_definitions(-DWIN32 -D_WIN32 -DMSWIN32 -DBGDWIN32
-                    -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0600)
+    add_definitions(-DWIN32 -D_WIN32 -DMSWIN32 -DBGDWIN32)
 endif()
 
 set(CMAKE_CXX_FLAGS_DEV "${USUAL_COMPILE_OPTIONS}"
diff --git a/cmake/FindOsmium.cmake b/cmake/FindOsmium.cmake
index 2224e18..0877ef2 100644
--- a/cmake/FindOsmium.cmake
+++ b/cmake/FindOsmium.cmake
@@ -298,6 +298,20 @@ unset(OSMIUM_EXTRA_FIND_VARS)
 
 #----------------------------------------------------------------------
 #
+#  A function for setting the -pthread option in compilers/linkers
+#
+#----------------------------------------------------------------------
+function(set_pthread_on_target _target)
+    if(NOT MSVC)
+        set_target_properties(${_target} PROPERTIES COMPILE_FLAGS "-pthread")
+        if(NOT APPLE)
+            set_target_properties(${_target} PROPERTIES LINK_FLAGS "-pthread")
+        endif()
+    endif()
+endfunction()
+
+#----------------------------------------------------------------------
+#
 #  Add compiler flags
 #
 #----------------------------------------------------------------------
diff --git a/man/osmium-add-locations-to-ways.md b/man/osmium-add-locations-to-ways.md
index 84829b7..f3ef23d 100644
--- a/man/osmium-add-locations-to-ways.md
+++ b/man/osmium-add-locations-to-ways.md
@@ -35,6 +35,10 @@ the right index type for small to medium sized extracts. For large
 (continent-sized) extracts or the full planet use `dense_mmap_array` on Linux
 or `dense_mem_array` on OSX/Windows.
 
+If the **--keep-untagged-nodes**, **-n** option is used, files created by this
+command can be updated with the **apply-changes** command using the
+**--locations-on-ways** option.
+
 This program will not work on full history files.
 
 
diff --git a/man/osmium-apply-changes.md b/man/osmium-apply-changes.md
index 0f7a883..69e8156 100644
--- a/man/osmium-apply-changes.md
+++ b/man/osmium-apply-changes.md
@@ -15,7 +15,7 @@ osmium-apply-changes - apply OSM change file(s) to OSM data file
 Merges the content of all OSM change files and applies those changes to the OSM
 data or history file.
 
-Objects in the data or historyy file must be sorted by type, ID, and version.
+Objects in the data or history file must be sorted by type, ID, and version.
 Objects in change files need not be sorted, so it doesn't matter in what order
 the change files are given or in what order they contain the data.
 
@@ -26,6 +26,20 @@ the **--with-history** option if that doesn't work.
 
 # OPTIONS
 
+-H, --with-history
+:   Update an OSM history file (instead of a normal OSM data file). Both
+    input and output must be history files. This option is usually not
+    necessary, because history files will be detected from their file name
+    suffixes, but if this detection doesn't work, you can force this mode
+    with this option. Can not be used together with the **--locations-on-ways**
+    option.
+
+--locations-on-ways
+:   Input has and output should have node locations on ways. Can be used
+    to update files created by the **osmium-add-locations-to-ways**. See
+    there for details on the format. Can not be used together with the
+    **--with-history**,**-H** option.
+
 -r, --remove-deleted
 :   Deprecated. Remove deleted objects from the output. This is now the
     default if your input file is a normal OSM data file ('.osm').
@@ -35,16 +49,23 @@ the **--with-history** option if that doesn't work.
     This is now the default if your input file is a normal OSM data file
     ('.osm').
 
---with-history
-:   Update an OSM history file (instead of a normal OSM data file). Both
-    input and output must be history files. This option is usually not
-    necessary, because history files will be detected from their file name
-    suffixes, but if this detection doesn't work, you can force this mode
-    with this option.
-
 
 @MAN_COMMON_OPTIONS@
- at MAN_INPUT_OPTIONS@
+# INPUT OPTIONS
+
+-F, --input-format=FORMAT
+:   The format of the OSM-DATA-FILE or OSM-HISTORY-FILE. Can be used to set
+    the input format if it can't be autodetected from the file name.
+    See **osmium-file-formats**(5) or the libosmium manual for details.
+
+--change-file-format=FORMAT
+:   The format of the OSM-CHANGE-FILE(s). Can be used to set the input format
+    if it can't be autodetected from the file name(s). This will set the format
+    for all change files, there is no way to set the format for some change
+    files only. See **osmium-file-formats**(5) or the libosmium manual for
+    details.
+
+
 @MAN_OUTPUT_OPTIONS@
 
 # DIAGNOSTICS
diff --git a/man/osmium-extract.md b/man/osmium-extract.md
index c426696..0264cc4 100644
--- a/man/osmium-extract.md
+++ b/man/osmium-extract.md
@@ -42,9 +42,8 @@ boundary, but between those vertices, might end up in the extract or not. In
 almost all cases this will be good enough, but if you want to make really sure
 you got everything, use a small buffer around your region.
 
-No **bounds** will be set in the header of the output file. Which bounds would
-be correct is unclear and setting it correctly might need an extra pass through
-the input file.
+By default no **bounds** will be set in the header of the output file. Use
+the --set-bounds option if you need this.
 
 Note that **osmium extract** will never clip any OSM objects, ie. it will not
 remove node references outside the region from ways or unused relation members
@@ -71,6 +70,10 @@ to merge several extracts without problems.
     are used, set the output directory and name with the --output/-o option
     in that case.
 
+-H, --with-history
+:   Specify that the input file is a history file. The output file(s) will also
+    be history file(s).
+
 -p, --polygon=POLYGON_FILE
 :   Set the polygon to cut out based on the contents of the file. The file
     has to be a GeoJSON, poly, or OSM file as described in the
@@ -86,9 +89,11 @@ to merge several extracts without problems.
 :   Set a named option for the strategy. If needed you can specify this
     option multiple times to set several options.
 
---with-history
-:   Specify that the input file is a history file. The output file(s) will also
-    be history file(s).
+--set-bounds
+:   Set the bounds field in the header. The bounds are set to the bbox or
+    envelope of the polygon specified for the extract. Note that strategies
+    other than "simple" can put nodes outside those bounds into the output
+    file.
 
 
 @MAN_COMMON_OPTIONS@
@@ -117,7 +122,9 @@ a region defined in a "bbox", "polygon" or "multipolygon" name. An optional
 "description" can be added, it will not be used by the program but can help
 with documenting the file contents. You can add an optional "output_format"
 if the format can not be detected from the "output" file name. Run "osmium
-help file-formats" to get a description of allowed formats.
+help file-formats" to get a description of allowed formats. The optional
+"output_header" allows you to set additional OSM file header settings such
+as the "generator".
 
     "extracts": [
         {
@@ -133,6 +140,9 @@ help file-formats" to get a description of allowed formats.
         },
         {
             "output": "munich.osm.pbf",
+            "output_header": {
+                "generator": "MyExtractor/1.0"
+            },
             "description": "optional description",
             "multipolygon": ...
         }
@@ -219,6 +229,10 @@ boxes, but more expensive for (multi)polygons. And it becomes more expensive
 the more vertices the (multi)polyon has. Use bounding boxes or simplified
 polygons where possible.
 
+Note that bounding boxes or (multi)polygons are not allowed to span the
+-180/180 degree line. If you need this, cut out the regions on each side and
+use **osmium merge** to join the resulting files.
+
 
 # (MULTI)POLYGON FILE FORMATS
 
@@ -307,9 +321,10 @@ Strategy **smart**
 
 For the **smart** strategy you can change the types of relations that should be
 reference-complete. Instead of just relations tagged "type=multipolygon", you
-can either get all relations (use "-S all") or give a list of types to the
--S option: "-S multipolygon,route". Note that especially boundary relations
-can be huge, so if you include them, be aware your result might be huge.
+can either get all relations (use "-S types=any") or give a list of types to
+the -S option: "-S types=multipolygon,route". Note that especially boundary
+relations can be huge, so if you include them, be aware your result might be
+huge.
 
 
 # DIAGNOSTICS
diff --git a/man/osmium-getid.md b/man/osmium-getid.md
index 67d0f9a..97208b9 100644
--- a/man/osmium-getid.md
+++ b/man/osmium-getid.md
@@ -51,12 +51,12 @@ Note that all objects will be taken from the *OSM-FILE*, the *ID-OSM-FILE* is
 only used to detect which objects to get. This might matter if there are
 different object versions in the different files.
 
-The *OSM-FILE* can not be a history file unless the **-H**, **--history**
+The *OSM-FILE* can not be a history file unless the **-H**, **--with-history**
 option is used. Then all versions of the objects will be copied to the output.
 
 If referenced objects are missing from the input file, the type and IDs
 of those objects is written out to *stderr* at the end of the program unless
-the **-H**, **--history** option was given.
+the **-H**, **--with-history** option was given.
 
 This command will not work with negative IDs.
 
@@ -68,7 +68,10 @@ This command will not work with negative IDs.
     (default: 'node'). It is also allowed to just use the first character
     of the type here.
 
--H, --history
+--history
+:   Deprecated. Use --with-history instead.
+
+-H, --with-history
 :   Make this program work on history files. This is only needed when using
     the **-r** option.
 
@@ -108,7 +111,8 @@ This command will not work with negative IDs.
 
 1
   ~ if there was an error processing the data or not all IDs were found,
-    (this is only detected if the **-h**, **--history** option was not used),
+    (this is only detected if the **-H**, **--with-history** option was not
+    used),
 
 2
   ~ if there was a problem with the command line arguments.
diff --git a/man/osmium-merge.md b/man/osmium-merge.md
index 2646633..90ef1cf 100644
--- a/man/osmium-merge.md
+++ b/man/osmium-merge.md
@@ -25,6 +25,10 @@ files as input creating a new history file. Do not use this command to merge
 non-history files with data from different points in time. It will not work
 correctly.
 
+While merging only object type, id, and version are compared. If you have
+objects with the same type, id, and version but different other data, the
+result of this command is undefined.
+
 @MAN_COMMON_OPTIONS@
 @MAN_INPUT_OPTIONS@
 @MAN_OUTPUT_OPTIONS@
diff --git a/man/osmium-tags-filter.md b/man/osmium-tags-filter.md
new file mode 100644
index 0000000..61aea97
--- /dev/null
+++ b/man/osmium-tags-filter.md
@@ -0,0 +1,156 @@
+
+# NAME
+
+osmium-tags-filter - filter objects matching specified keys/tags
+
+
+# SYNOPSIS
+
+**osmium tags-filter** \[*OPTIONS*\] *OSM-FILE* *FILTER-EXPRESSION*...\
+**osmium tags-filter** \[*OPTIONS*\] --expressions=*FILE* *OSM-FILE*
+
+
+# DESCRIPTION
+
+Get objects matching the specified expressions from the input and write them to
+the output. Expressions can either be specified on the command line or in an
+expressions file. See the **FILTER EXPRESSIONS** section for a description of
+the filter expression format.
+
+All objects matching the expressions will be read from *OSM-FILE* and written
+to the output. All objects referenced from those objects will also be added
+to the output unless the option **-R**, **--omit-referenced** is used. This
+applies to nodes referenced in ways and members referenced in relations.
+
+If the option **-R**, **--omit-referenced** is used, the input file is read
+only once, otherwise the input file will possibly be read up to three times.
+
+Objects will be written out in the order they are found in the *OSM-FILE*.
+
+The command will only work correctly on history files if the
+**-R**/**--omit-referenced** option is used.
+
+
+# OPTIONS
+
+-e FILE, --expressions=FILE
+:   Read expressions from the specified file, one per line. Empty lines are
+    ignored. Everything after the comment character (#) is also ignored. The
+    the **FILTER EXPRESSIONS** section for further details.
+
+-i, --invert-match
+:   Invert the sense of matching. Exclude all objects with matching tags.
+
+-R, --omit-referenced
+:   Omit the nodes referenced from matching ways and members referenced from
+    matching relations.
+
+ at MAN_COMMON_OPTIONS@
+ at MAN_PROGRESS_OPTIONS@
+ at MAN_INPUT_OPTIONS@
+ at MAN_OUTPUT_OPTIONS@
+
+
+# FILTER EXPRESSIONS
+
+A filter expression specifies a tag or tags that should be found in the data
+and the type of object (node, way, or relation) that should be matched.
+
+The object type(s) comes first, then a slash (/) and then the rest of the
+expression. Object types are specified as 'n' (for nodes), 'w' (for ways), and
+'r' (for relations). Any combination of them can be used. If the object type is
+not specified, the expression matches all object types.
+
+Some examples:
+
+n/amenity
+:   Matches all nodes with the key "amenity".
+
+nw/highway
+:   Matches all nodes or ways with the key "highway".
+
+/note
+:   Matches objects of any type with the key "note".
+
+note
+:   Matches objects of any type with the key "note".
+
+w/highway=primary
+:   Matches all ways with the key "highway" and value "primary".
+
+w/highway!=primary
+:   Matches all ways with the key "highway" and a value other than "primary".
+
+r/type=multipolygon,boundary
+:   Matches all relations with key "type" and value "multipolygon" or "boundary".
+
+w/name,name:de=Kastanienallee,Kastanienstrasse
+:   Matches any way with a "name" or "name:de" tag with the value
+    "Kastanienallee" or "Kastanienstrasse".
+
+n/addr:\*
+:   Matches all nodes with any key starting with "addr:"
+
+n/name=\*Paris\*
+:   Matches all nodes with a name that contains the word "Paris".
+
+If there is no equal sign ("=") in the expression only keys are matched and
+values can by anything. If there is an equal sign ("=") in the expression, the
+key is to the left and the value to the right. An exclamation sign ("!") before
+the equal sign means: A tag with that key, but not the value(s) to the right of
+the equal sign. A leading or trailing asterisk ("\*") can be used for substring
+or prefix matching, respectively. Commas (",") can be used to separate several
+keys or values.
+
+All filter expressions are case-sensitive. There is no way to escape the
+special characters such as "=", "\*" and ",". You can not mix
+comma-expressions and "\*"-expressions.
+
+The filter expressions specified in a file and/or on the command line are
+matched in the order they are given. To achieve best performance, put
+expressions expected to match more often first.
+
+
+# DIAGNOSTICS
+
+**osmium tags-filter** exits with exit code
+
+0
+  ~ if everything went alright,
+
+1
+  ~ if there was an error processing the data, or
+
+2
+  ~ if there was a problem with the command line arguments.
+
+
+# MEMORY USAGE
+
+**osmium tags-filter** does all its work on the fly and only keeps tables of
+object IDs it needs in main memory. If the **-R**/**--omit-referenced** option
+is used, no IDs are kept in memory.
+
+
+# EXAMPLES
+
+Get all amenity nodes from the Berlin PBF file:
+
+    osmium tags-filter -o amenties.osm.pbf berlin.osm.pbf n/amenity
+
+Get all objects (nodes, ways, or relations) with a `note` tag:
+
+    osmium tags-filter -R -o notes.osm.pbf berlin.osm.pbf note
+
+Get all nodes and ways with a `highway` tag and all relations tagged with
+`type=restriction` plus all referenced objects:
+
+    osmium tags-filter -o filtered.osm.pbf planet.osm.pbf \
+        nw/highway r/type=restriction
+
+
+# SEE ALSO
+
+* **osmium**(1), **osmium-file-formats**(5)
+* [Osmium website](http://osmcode.org/osmium-tool/)
+
diff --git a/man/osmium.md b/man/osmium.md
index b6754e4..e1749a2 100644
--- a/man/osmium.md
+++ b/man/osmium.md
@@ -75,6 +75,9 @@ show
 sort
 :   sort OSM files
 
+tags-filter
+:   filter OSM data based on tags
+
 time-filter
 :   filter OSM data by time from a history file
 
@@ -124,6 +127,7 @@ If an osmium command exits with an "Out of memory" error, try running it with
   **osmium-renumber**(1),
   **osmium-show**(1),
   **osmium-sort**(1),
+  **osmium-tags-filter**(1),
   **osmium-time-filter**(1),
   **osmium-file-formats**(5)
 * [Osmium website](http://osmcode.org/osmium-tool/)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8c84e6e..c9fbbef 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -12,5 +12,7 @@ add_executable(osmium ${OSMIUM_SOURCE_FILES})
 
 target_link_libraries(osmium ${Boost_LIBRARIES} ${OSMIUM_LIBRARIES})
 
+set_pthread_on_target(osmium)
+
 install(TARGETS osmium DESTINATION bin)
 
diff --git a/src/command_apply_changes.cpp b/src/command_apply_changes.cpp
index 701e883..46be0b2 100644
--- a/src/command_apply_changes.cpp
+++ b/src/command_apply_changes.cpp
@@ -28,6 +28,8 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <boost/function_output_iterator.hpp>
 #include <boost/program_options.hpp>
 
+#include <osmium/index/id_set.hpp>
+#include <osmium/index/map/sparse_mem_array.hpp>
 #include <osmium/io/file.hpp>
 #include <osmium/io/header.hpp>
 #include <osmium/io/input_iterator.hpp>
@@ -40,6 +42,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <osmium/osm/object.hpp>
 #include <osmium/osm/object_comparisons.hpp>
 #include <osmium/osm/types.hpp>
+#include <osmium/util/progress_bar.hpp>
 #include <osmium/util/verbose_output.hpp>
 #include <osmium/visitor.hpp>
 
@@ -47,12 +50,16 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include "exception.hpp"
 #include "util.hpp"
 
+using location_index_type = osmium::index::map::SparseMemArray<osmium::unsigned_object_id_type, osmium::Location>;
+
 bool CommandApplyChanges::setup(const std::vector<std::string>& arguments) {
     po::options_description opts_cmd{"COMMAND OPTIONS"};
     opts_cmd.add_options()
-    ("simplify,s",       "Simplify change (deprecated)")
-    ("remove-deleted,r", "Remove deleted objects from output (deprecated)")
-    ("with-history",     "Apply changes to history file")
+    ("change-file-format", po::value<std::string>(), "Format of the change file(s)")
+    ("simplify,s",        "Simplify change (deprecated)")
+    ("remove-deleted,r",  "Remove deleted objects from output (deprecated)")
+    ("with-history,H",    "Apply changes to history file")
+    ("locations-on-ways", "Expect and update locations on ways")
     ;
 
     po::options_description opts_common{add_common_options()};
@@ -89,7 +96,18 @@ bool CommandApplyChanges::setup(const std::vector<std::string>& arguments) {
         throw argument_error{"Need data file and at least one change file on the command line."};
     }
 
+    if (vm.count("change-file-format")) {
+        m_change_file_format = vm["change-file-format"].as<std::string>();
+    }
+
+    if (vm.count("locations-on-ways")) {
+        m_locations_on_ways = true;
+    }
+
     if (vm.count("with-history")) {
+        if (m_locations_on_ways) {
+            throw argument_error{"Can not use --with-history/-H and --locations-on-ways together."};
+        }
         m_with_history = true;
         m_output_file.set_has_multiple_object_versions(true);
     } else {
@@ -119,9 +137,11 @@ void CommandApplyChanges::show_arguments() {
     for (const auto& fn : m_change_filenames) {
         m_vout << "    " << fn << "\n";
     }
-    m_vout << "  input format: " << m_input_format << "\n";
+    m_vout << "  data file format: " << m_input_format << "\n";
+    m_vout << "  change file format: " << m_change_file_format << "\n";
     show_output_arguments(m_vout);
     m_vout << "  reading and writing history file: " << yes_no(m_with_history);
+    m_vout << "  locations on ways: " << yes_no(m_locations_on_ways);
 }
 
 namespace {
@@ -157,6 +177,19 @@ namespace {
 
 } // anonymous namespace
 
+static void update_nodes_if_way(osmium::OSMObject& object, const location_index_type& location_index) {
+    if (object.type() != osmium::item_type::way) {
+        return;
+    }
+
+    for (auto& node_ref : static_cast<osmium::Way&>(object).nodes()) {
+        auto location = location_index.get_noexcept(node_ref.positive_ref());
+        if (location) {
+            node_ref.set_location(location);
+        }
+    }
+}
+
 bool CommandApplyChanges::run() {
     std::vector<osmium::memory::Buffer> changes;
     osmium::ObjectPointerCollection objects;
@@ -164,7 +197,12 @@ bool CommandApplyChanges::run() {
     m_vout << "Reading change file contents...\n";
 
     for (const std::string& change_file_name : m_change_filenames) {
-        osmium::io::Reader reader{change_file_name, osmium::osm_entity_bits::object};
+        if (change_file_name == "-" && m_change_file_format.empty()) {
+            throw argument_error{"When reading the change file from STDIN you have to use\n"
+                                 "the --change-file-format option to specify the file format."};
+        }
+        osmium::io::File file{change_file_name, m_change_file_format};
+        osmium::io::Reader reader{file, osmium::osm_entity_bits::object};
         while (osmium::memory::Buffer buffer = reader.read()) {
             osmium::apply(buffer, objects);
             changes.push_back(std::move(buffer));
@@ -178,17 +216,20 @@ bool CommandApplyChanges::run() {
     osmium::io::Header header{reader.header()};
     setup_header(header);
 
+    if (m_locations_on_ways) {
+        m_output_file.set("locations_on_ways");
+    }
+
     m_vout << "Opening output file...\n";
     osmium::io::Writer writer{m_output_file, header, m_output_overwrite, m_fsync};
 
-    auto input = osmium::io::make_input_iterator_range<osmium::OSMObject>(reader);
-
     if (m_with_history) {
         // For history files this is a straightforward sort of the change
         // files followed by a merge with the input file.
         m_vout << "Sorting change data...\n";
         objects.sort(osmium::object_order_type_id_version());
 
+        auto input = osmium::io::make_input_iterator_range<osmium::OSMObject>(reader);
         auto out = osmium::io::make_output_iterator(writer);
         m_vout << "Applying changes and writing them to output...\n";
         std::set_union(objects.begin(),
@@ -201,19 +242,98 @@ bool CommandApplyChanges::run() {
         // object first and then only copy this last version of any object
         // to the output.
         m_vout << "Sorting change data...\n";
-        objects.sort(osmium::object_order_type_id_reverse_version());
-
-        auto output_it = boost::make_function_output_iterator(
-                            copy_first_with_id(writer)
-        );
-
-        m_vout << "Applying changes and writing them to output...\n";
-        std::set_union(objects.begin(),
-                       objects.end(),
-                       input.begin(),
-                       input.end(),
-                       output_it,
-                       osmium::object_order_type_id_reverse_version());
+        objects.sort(osmium::object_order_type_id_reverse_version{});
+
+        if (m_locations_on_ways) {
+            objects.unique(osmium::object_equal_type_id{});
+            m_vout << "There are " << objects.size() << " unique objects in the change files\n";
+
+            osmium::index::IdSetSmall<osmium::unsigned_object_id_type> node_ids;
+            m_vout << "Creating node index...\n";
+            for (const auto& buffer : changes) {
+                for (const auto& way : buffer.select<osmium::Way>()) {
+                    for (const auto& nr : way.nodes()) {
+                        node_ids.set(nr.positive_ref());
+                    }
+                }
+            }
+            node_ids.sort_unique();
+            m_vout << "Node index has " << node_ids.size() << " entries\n";
+
+            m_vout << "Creating location index...\n";
+            location_index_type location_index;
+            for (const auto& buffer : changes) {
+                for (const auto& node : buffer.select<osmium::Node>()) {
+                    location_index.set(node.positive_id(), node.location());
+                }
+            }
+            m_vout << "Location index has " << location_index.size() << " entries\n";
+
+            m_vout << "Applying changes and writing them to output...\n";
+            auto it = objects.begin();
+            auto last_type = osmium::item_type::undefined;
+            osmium::ProgressBar progress_bar{reader.file_size(), display_progress()};
+            while (osmium::memory::Buffer buffer = reader.read()) {
+                progress_bar.update(reader.offset());
+                for (auto& object : buffer.select<osmium::OSMObject>()) {
+                    if (object.type() < last_type) {
+                        throw std::runtime_error{"Input data out of order. Need nodes, ways, relations in ID order."};
+                    }
+                    if (object.type() == osmium::item_type::node) {
+                        const auto& node = static_cast<osmium::Node&>(object);
+                        if (node_ids.get_binary_search(node.positive_id())) {
+                            const auto location = location_index.get_noexcept(node.positive_id());
+                            if (!location) {
+                                location_index.set(node.positive_id(), node.location());
+                            }
+                        }
+                    } else if (object.type() == osmium::item_type::way) {
+                        if (last_type == osmium::item_type::node) {
+                            location_index.sort();
+                            node_ids.clear();
+                        }
+                    }
+
+                    last_type = object.type();
+
+                    auto last_it = it;
+                    while (it != objects.end() && osmium::object_order_type_id_reverse_version{}(*it, object)) {
+                        if (it->visible()) {
+                            update_nodes_if_way(*it, location_index);
+                            writer(*it);
+                        }
+                        last_it = it;
+                        ++it;
+                    }
+
+                    if (last_it == objects.end() || last_it->type() != object.type() || last_it->id() != object.id()) {
+                        update_nodes_if_way(object, location_index);
+                        writer(object);
+                    }
+                }
+            }
+            while (it != objects.end()) {
+                if (it->visible()) {
+                    update_nodes_if_way(*it, location_index);
+                    writer(*it);
+                }
+                ++it;
+            }
+            progress_bar.done();
+        } else {
+            auto input = osmium::io::make_input_iterator_range<osmium::OSMObject>(reader);
+            auto output_it = boost::make_function_output_iterator(
+                                copy_first_with_id(writer)
+            );
+
+            m_vout << "Applying changes and writing them to output...\n";
+            std::set_union(objects.begin(),
+                           objects.end(),
+                           input.begin(),
+                           input.end(),
+                           output_it,
+                           osmium::object_order_type_id_reverse_version());
+        }
     }
 
     writer.close();
diff --git a/src/command_apply_changes.hpp b/src/command_apply_changes.hpp
index f38cac7..bf4386b 100644
--- a/src/command_apply_changes.hpp
+++ b/src/command_apply_changes.hpp
@@ -32,7 +32,10 @@ class CommandApplyChanges : public Command, public with_single_osm_input, public
 
     std::vector<std::string> m_change_filenames;
 
+    std::string m_change_file_format;
+
     bool m_with_history = false;
+    bool m_locations_on_ways = false;
 
 public:
 
diff --git a/src/command_cat.cpp b/src/command_cat.cpp
index bb2e3b3..c6763e0 100644
--- a/src/command_cat.cpp
+++ b/src/command_cat.cpp
@@ -20,8 +20,8 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 */
 
-#include <algorithm>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <boost/program_options.hpp>
diff --git a/src/command_check_refs.cpp b/src/command_check_refs.cpp
index 92f62e4..70d4df4 100644
--- a/src/command_check_refs.cpp
+++ b/src/command_check_refs.cpp
@@ -20,8 +20,8 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 */
 
-#include <cstdint>
 #include <algorithm>
+#include <cstdint>
 #include <iostream>
 #include <string>
 #include <utility>
diff --git a/src/command_extract.cpp b/src/command_extract.cpp
index 6d51212..1545613 100644
--- a/src/command_extract.cpp
+++ b/src/command_extract.cpp
@@ -273,6 +273,22 @@ void CommandExtract::parse_config_file() {
             } else {
                 throw config_error{"Missing geometry for extract. Need 'bbox', 'polygon', or 'multipolygon'."};
             }
+
+            Extract& extract = *m_extracts.back();
+            const auto json_output_header = e.FindMember("output_header");
+            if (json_output_header != e.MemberEnd()) {
+                const auto& value = json_output_header->value;
+                if (!value.IsObject()) {
+                    throw config_error{"Optional 'output_header' field must be an object."};
+                }
+                for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) {
+                    const auto& member_value = it->value;
+                    if (!member_value.IsString()) {
+                        throw config_error{"Values in 'output_header' object must be strings."};
+                    }
+                    extract.add_header_option(it->name.GetString(), member_value.GetString());
+                }
+            }
         } catch (const config_error& e) {
             std::string message{"In extract "};
             message += std::to_string(extract_num);
@@ -330,7 +346,8 @@ bool CommandExtract::setup(const std::vector<std::string>& arguments) {
     ("option,S", po::value<std::vector<std::string>>(), "Set strategy option")
     ("polygon,p", po::value<std::string>(), "Polygon file")
     ("strategy,s", po::value<std::string>()->default_value("complete_ways"), "Use named extract strategy")
-    ("with-history", "Input file and output files are history files")
+    ("with-history,H", "Input file and output files are history files")
+    ("set-bounds", "Sets bounds (bounding box) in header")
     ;
 
     po::options_description opts_common{add_common_options()};
@@ -369,10 +386,10 @@ bool CommandExtract::setup(const std::vector<std::string>& arguments) {
             set_directory(vm["directory"].as<std::string>());
         }
         if (vm.count("output")) {
-            warning("Ignoring --output/-o option\n");
+            warning("Ignoring --output/-o option.\n");
         }
         if (vm.count("output-format")) {
-            warning("Ignoring --output-format/-f option\n");
+            warning("Ignoring --output-format/-f option.\n");
         }
         m_config_file_name = vm["config"].as<std::string>();
         auto slash = m_config_file_name.find_last_of('/');
@@ -391,7 +408,7 @@ bool CommandExtract::setup(const std::vector<std::string>& arguments) {
 
     if (vm.count("bbox")) {
         if (vm.count("directory")) {
-            warning("Ignoring --directory/-d option\n");
+            warning("Ignoring --directory/-d option.\n");
         }
         check_output_file();
         m_extracts.emplace_back(new ExtractBBox{m_output_file, "", parse_bbox(vm["bbox"].as<std::string>())});
@@ -399,7 +416,7 @@ bool CommandExtract::setup(const std::vector<std::string>& arguments) {
 
     if (vm.count("polygon")) {
         if (vm.count("directory")) {
-            warning("Ignoring --directory/-d option\n");
+            warning("Ignoring --directory/-d option.\n");
         }
         check_output_file();
         m_extracts.emplace_back(new ExtractPolygon{m_output_file, "", m_buffer, parse_multipolygon_object("./", vm["polygon"].as<std::string>(), "", m_buffer)});
@@ -415,6 +432,10 @@ bool CommandExtract::setup(const std::vector<std::string>& arguments) {
         m_with_history = true;
     }
 
+    if (vm.count("set-bounds")) {
+        m_set_bounds = true;
+    }
+
     if (vm.count("strategy")) {
         m_strategy = make_strategy(vm["strategy"].as<std::string>());
     }
@@ -446,6 +467,18 @@ void CommandExtract::show_arguments() {
         std::cerr.fill(old_fill);
         m_vout << "     Format:      " << e->output_format()    << '\n';
         m_vout << "     Description: " << e->description()      << '\n';
+        if (!e->header_options().empty()) {
+        m_vout << "     Header opts: ";
+            bool first = true;
+            for (const auto& opt : e->header_options()) {
+                if (first) {
+                    first = false;
+                } else {
+                    m_vout << "                  ";
+                }
+                m_vout << opt.first << ": " << opt.second << '\n';
+            }
+        }
         m_vout << "     Envelope:    " << e->envelope_as_text() << '\n';
         m_vout << "     Type:        " << e->geometry_type()    << '\n';
         m_vout << "     Geometry:    " << e->geometry_as_text() << '\n';
@@ -460,7 +493,14 @@ bool CommandExtract::run() {
     setup_header(header);
 
     for (const auto& extract : m_extracts) {
-        extract->open_file(header, m_output_overwrite, m_fsync);
+        osmium::io::Header file_header{header};
+        if (m_set_bounds) {
+            file_header.add_box(extract->envelope());
+        }
+        for (const auto& p : extract->header_options()) {
+            file_header.set(p.first, p.second);
+        }
+        extract->open_file(file_header, m_output_overwrite, m_fsync);
     }
 
     m_strategy->run(m_vout, display_progress(), m_input_file);
diff --git a/src/command_extract.hpp b/src/command_extract.hpp
index 7665b97..17e0197 100644
--- a/src/command_extract.hpp
+++ b/src/command_extract.hpp
@@ -46,6 +46,7 @@ class CommandExtract : public Command, public with_single_osm_input, public with
     std::vector<std::unique_ptr<Extract>> m_extracts;
     osmium::memory::Buffer m_buffer{initial_buffer_size, osmium::memory::Buffer::auto_grow::yes};
     bool m_with_history = false;
+    bool m_set_bounds = false;
 
     void parse_config_file();
 
diff --git a/src/command_fileinfo.cpp b/src/command_fileinfo.cpp
index 70734c4..edad023 100644
--- a/src/command_fileinfo.cpp
+++ b/src/command_fileinfo.cpp
@@ -28,8 +28,6 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <memory>
 #include <sstream>
 #include <string>
-#include <sys/stat.h>
-#include <sys/types.h>
 #include <system_error>
 #include <utility>
 #include <vector>
@@ -54,6 +52,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <osmium/osm.hpp>
 #include <osmium/osm/box.hpp>
 #include <osmium/osm/crc.hpp>
+#include <osmium/util/file.hpp>
 #include <osmium/util/minmax.hpp>
 #include <osmium/util/progress_bar.hpp>
 #include <osmium/util/verbose_output.hpp>
@@ -74,10 +73,10 @@ struct InfoHandler : public osmium::handler::Handler {
     uint64_t ways       = 0;
     uint64_t relations  = 0;
 
-    osmium::max_op<osmium::object_id_type> largest_changeset_id { 0 };
-    osmium::max_op<osmium::object_id_type> largest_node_id { 0 };
-    osmium::max_op<osmium::object_id_type> largest_way_id { 0 };
-    osmium::max_op<osmium::object_id_type> largest_relation_id { 0 };
+    osmium::max_op<osmium::object_id_type> largest_changeset_id{0};
+    osmium::max_op<osmium::object_id_type> largest_node_id{0};
+    osmium::max_op<osmium::object_id_type> largest_way_id{0};
+    osmium::max_op<osmium::object_id_type> largest_relation_id{0};
 
     osmium::min_op<osmium::Timestamp> first_timestamp;
     osmium::max_op<osmium::Timestamp> last_timestamp;
@@ -149,23 +148,6 @@ struct InfoHandler : public osmium::handler::Handler {
 
 }; // struct InfoHandler
 
-/*************************************************************************/
-
-off_t filesize(const std::string& filename) {
-    if (filename.empty()) {
-        return 0;
-    }
-
-    struct stat s;
-    if (::stat(filename.c_str(), &s) == -1) {
-        throw std::system_error{errno, std::system_category(), std::string{"Could not get file size of file '"} + filename + "'"};
-    }
-
-    return s.st_size;
-}
-
-/*************************************************************************/
-
 class Output {
 
 public:
@@ -192,7 +174,7 @@ public:
         std::cout << "  Compression: " << input_file.compression() << "\n";
 
         if (!input_file.filename().empty()) {
-            std::cout << "  Size: " << filesize(input_file.filename()) << "\n";
+            std::cout << "  Size: " << osmium::util::file_size(input_file.filename()) << "\n";
         }
     }
 
@@ -279,7 +261,7 @@ public:
 
         if (!input_file.filename().empty()) {
             m_writer.String("size");
-            m_writer.Int64(filesize(input_file.filename()));
+            m_writer.Int64(osmium::util::file_size(input_file.filename()));
         }
 
         m_writer.EndObject();
@@ -411,7 +393,7 @@ public:
             if (input_file.filename().empty()) {
                 std::cout << 0 << "\n";
             } else {
-                std::cout << filesize(input_file.filename()) << "\n";
+                std::cout << osmium::util::file_size(input_file.filename()) << "\n";
             }
         }
     }
diff --git a/src/command_getid.cpp b/src/command_getid.cpp
index 1c47b34..8058549 100644
--- a/src/command_getid.cpp
+++ b/src/command_getid.cpp
@@ -23,14 +23,13 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <cstddef>
 #include <fstream>
 #include <iostream>
-#include <map>
-#include <set>
 #include <string>
 #include <utility>
 #include <vector>
 
 #include <boost/program_options.hpp>
 
+#include <osmium/index/relations_map.hpp>
 #include <osmium/io/header.hpp>
 #include <osmium/io/reader.hpp>
 #include <osmium/io/writer.hpp>
@@ -48,7 +47,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 void CommandGetId::parse_and_add_id(const std::string& s) {
     auto p = osmium::string_to_object_id(s.c_str(), osmium::osm_entity_bits::nwr, m_default_item_type);
     if (p.second < 0) {
-        throw std::runtime_error("osmium-getid does not work with negative IDs");
+        throw std::runtime_error{"osmium-getid does not work with negative IDs"};
     }
     m_ids(p.first).set(p.second);
 }
@@ -56,7 +55,7 @@ void CommandGetId::parse_and_add_id(const std::string& s) {
 void CommandGetId::read_id_file(std::istream& stream) {
     m_vout << "Reading ID file...\n";
     for (std::string line; std::getline(stream, line); ) {
-        auto pos = line.find_first_of(" #");
+        const auto pos = line.find_first_of(" #");
         if (pos != std::string::npos) {
             line.erase(pos);
         }
@@ -84,7 +83,8 @@ bool CommandGetId::setup(const std::vector<std::string>& arguments) {
     ("default-type", po::value<std::string>()->default_value("node"), "Default item type")
     ("id-file,i", po::value<std::vector<std::string>>(), "Read OSM IDs from text file")
     ("id-osm-file,I", po::value<std::vector<std::string>>(), "Read OSM IDs from OSM file")
-    ("history,H", "Make it work with history files")
+    ("history", "Deprecated, use --with-history instead")
+    ("with-history,H", "Make it work with history files")
     ("add-referenced,r", "Recursively add referenced objects")
     ("verbose-ids", "Print all requested and missing IDs")
     ;
@@ -125,7 +125,12 @@ bool CommandGetId::setup(const std::vector<std::string>& arguments) {
         m_add_referenced_objects = true;
     }
 
+    if (vm.count("with-history")) {
+        m_work_with_history = true;
+    }
+
     if (vm.count("history")) {
+        warning("The --history option is deprecated. Use --with-history instead.\n");
         m_work_with_history = true;
     }
 
@@ -275,25 +280,24 @@ void CommandGetId::read_id_osm_file(const std::string& file_name) {
     reader.close();
 }
 
-void CommandGetId::mark_rel_ids(const std::multimap<osmium::object_id_type, osmium::object_id_type>& rel_in_rel, osmium::object_id_type id) {
-    auto range = rel_in_rel.equal_range(id);
-    for (auto it = range.first; it != range.second; ++it) {
-        if (m_ids(osmium::item_type::relation).check_and_set(it->second)) {
-            mark_rel_ids(rel_in_rel, it->second);
+void CommandGetId::mark_rel_ids(const osmium::index::RelationsMapIndex& rel_in_rel, osmium::object_id_type parent_id) {
+    rel_in_rel.for_each(parent_id, [&](osmium::unsigned_object_id_type member_id) {
+        if (m_ids(osmium::item_type::relation).check_and_set(member_id)) {
+            mark_rel_ids(rel_in_rel, member_id);
         }
-    }
+    });
 }
 
 bool CommandGetId::find_relations_in_relations() {
     m_vout << "  Reading input file to find relations in relations...\n";
-    std::multimap<osmium::object_id_type, osmium::object_id_type> rel_in_rel;
+    osmium::index::RelationsMapStash stash;
 
     osmium::io::Reader reader{m_input_file, osmium::osm_entity_bits::relation};
     while (osmium::memory::Buffer buffer = reader.read()) {
         for (const auto& relation : buffer.select<osmium::Relation>()) {
             for (const auto& member : relation.members()) {
                 if (member.type() == osmium::item_type::relation) {
-                    rel_in_rel.emplace(relation.id(), member.ref());
+                    stash.add(member.ref(), relation.id());
                 } else if (m_ids(osmium::item_type::relation).get(relation.positive_id())) {
                     if (member.type() == osmium::item_type::node) {
                         m_ids(osmium::item_type::node).set(member.positive_ref());
@@ -306,10 +310,11 @@ bool CommandGetId::find_relations_in_relations() {
     }
     reader.close();
 
-    if (rel_in_rel.empty()) {
+    if (stash.empty()) {
         return false;
     }
 
+    const auto rel_in_rel = stash.build_parent_to_member_index();
     for (const osmium::unsigned_object_id_type id : m_ids(osmium::item_type::relation)) {
         mark_rel_ids(rel_in_rel, id);
     }
@@ -353,12 +358,19 @@ void CommandGetId::find_nodes_in_ways() {
 
 void CommandGetId::find_referenced_objects() {
     m_vout << "Following references...\n";
+
+    // If there are any relations we are looking for, we need to run
+    // find_relations_in_relations() to get the member IDs of all types.
     bool todo = !m_ids(osmium::item_type::relation).empty();
     if (todo) {
         todo = find_relations_in_relations();
     }
 
     if (todo) {
+        // If find_relations_in_relations() returned true, it means it found
+        // relation members that were not in the original relations ID list.
+        // This means we need to run find_nodes_and_ways_in_relations() to
+        // make sure we have all node and way members of those relations, too.
         find_nodes_and_ways_in_relations();
     }
 
@@ -382,7 +394,7 @@ bool CommandGetId::run() {
 
     osmium::io::Writer writer{m_output_file, header, m_output_overwrite, m_fsync};
 
-    m_vout << "Copying from source to output file...\n";
+    m_vout << "Copying matching objects to output file...\n";
     osmium::ProgressBar progress_bar{reader.file_size(), display_progress()};
     while (osmium::memory::Buffer buffer = reader.read()) {
         progress_bar.update(reader.offset());
diff --git a/src/command_getid.hpp b/src/command_getid.hpp
index 44943c0..d04d7ef 100644
--- a/src/command_getid.hpp
+++ b/src/command_getid.hpp
@@ -23,19 +23,23 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 */
 
-#include <map>
 #include <set>
 #include <string>
 #include <vector>
 
 #include <osmium/fwd.hpp>
 #include <osmium/index/id_set.hpp>
+#include <osmium/index/nwr_array.hpp>
 #include <osmium/osm/entity_bits.hpp>
 #include <osmium/osm/item_type.hpp>
 #include <osmium/osm/types.hpp>
 
 #include "cmd.hpp" // IWYU pragma: export
 
+namespace osmium { namespace index {
+    class RelationsMapIndex;
+}}
+
 class CommandGetId : public Command, public with_single_osm_input, public with_osm_output {
 
     osmium::item_type m_default_item_type = osmium::item_type::node;
@@ -44,7 +48,7 @@ class CommandGetId : public Command, public with_single_osm_input, public with_o
     bool m_work_with_history = false;
     bool m_verbose_ids = false;
 
-    osmium::index::NWRIdSet<osmium::index::IdSetDense> m_ids;
+    osmium::nwr_array<osmium::index::IdSetDense<osmium::unsigned_object_id_type>> m_ids;
 
     void parse_and_add_id(const std::string& s);
 
@@ -60,7 +64,7 @@ class CommandGetId : public Command, public with_single_osm_input, public with_o
     void add_nodes(const osmium::Way& way);
     void add_members(const osmium::Relation& relation);
 
-    void mark_rel_ids(const std::multimap<osmium::object_id_type, osmium::object_id_type>& rel_in_rel, osmium::object_id_type id);
+    void mark_rel_ids(const osmium::index::RelationsMapIndex& rel_in_rel, osmium::object_id_type parent_id);
     bool find_relations_in_relations();
     void find_nodes_and_ways_in_relations();
     void find_nodes_in_ways();
diff --git a/src/command_merge.cpp b/src/command_merge.cpp
index 7da7193..66c262d 100644
--- a/src/command_merge.cpp
+++ b/src/command_merge.cpp
@@ -25,6 +25,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <queue>
 #include <string>
 #include <vector>
+#include <utility>
 
 #include <boost/program_options.hpp>
 
diff --git a/src/command_merge_changes.cpp b/src/command_merge_changes.cpp
index 73938b8..cd99115 100644
--- a/src/command_merge_changes.cpp
+++ b/src/command_merge_changes.cpp
@@ -22,6 +22,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 #include <algorithm>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <boost/program_options.hpp>
diff --git a/src/command_show.cpp b/src/command_show.cpp
index 5bb18d5..c35e795 100644
--- a/src/command_show.cpp
+++ b/src/command_show.cpp
@@ -21,6 +21,10 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
 #include <cstdlib>
+#include <string>
+#include <system_error>
+#include <vector>
+#include <utility>
 
 #ifndef _MSC_VER
 # include <signal.h>
diff --git a/src/command_sort.cpp b/src/command_sort.cpp
index 6f87520..8a07856 100644
--- a/src/command_sort.cpp
+++ b/src/command_sort.cpp
@@ -22,6 +22,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 #include <algorithm>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <boost/program_options.hpp>
diff --git a/src/command_tags_filter.cpp b/src/command_tags_filter.cpp
new file mode 100644
index 0000000..9ba2761
--- /dev/null
+++ b/src/command_tags_filter.cpp
@@ -0,0 +1,385 @@
+/*
+
+Osmium -- OpenStreetMap data manipulation command line tool
+http://osmcode.org/osmium-tool/
+
+Copyright (C) 2013-2017  Jochen Topf <jochen at topf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+*/
+
+#include <cstddef>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <boost/program_options.hpp>
+
+#include <osmium/index/relations_map.hpp>
+#include <osmium/io/header.hpp>
+#include <osmium/io/reader.hpp>
+#include <osmium/io/writer.hpp>
+#include <osmium/memory/buffer.hpp>
+#include <osmium/osm.hpp>
+#include <osmium/osm/types_from_string.hpp>
+#include <osmium/tags/taglist.hpp>
+#include <osmium/util/progress_bar.hpp>
+#include <osmium/util/string.hpp>
+#include <osmium/util/verbose_output.hpp>
+
+#include "command_tags_filter.hpp"
+#include "exception.hpp"
+#include "util.hpp"
+
+void CommandTagsFilter::add_filter(osmium::osm_entity_bits::type entities, const osmium::TagMatcher& matcher) {
+    if (entities & osmium::osm_entity_bits::node) {
+        m_filters(osmium::item_type::node).add_rule(true, matcher);
+    }
+    if (entities & osmium::osm_entity_bits::way) {
+        m_filters(osmium::item_type::way).add_rule(true, matcher);
+    }
+    if (entities & osmium::osm_entity_bits::relation) {
+        m_filters(osmium::item_type::relation).add_rule(true, matcher);
+    }
+}
+
+void strip_whitespace(std::string& string) {
+    while (!string.empty() && string.back() == ' ') {
+        string.pop_back();
+    }
+
+    const auto pos = string.find_first_not_of(' ');
+    if (pos != std::string::npos) {
+        string.erase(0, pos);
+    }
+}
+
+osmium::StringMatcher get_matcher(std::string string) {
+    strip_whitespace(string);
+
+    if (string.size() == 1 && string.front() == '*') {
+        return osmium::StringMatcher::always_true{};
+    }
+
+    if (string.empty() || (string.back() != '*' && string.front() != '*')) {
+        if (string.find(',') == std::string::npos) {
+            return osmium::StringMatcher::equal{string};
+        }
+        auto sstrings = osmium::split_string(string, ',');
+        for (auto& s : sstrings) {
+            strip_whitespace(s);
+        }
+        return osmium::StringMatcher::list{sstrings};
+    }
+
+    auto s = string;
+
+    if (s.back() == '*' && s.front() != '*') {
+        s.pop_back();
+        return osmium::StringMatcher::prefix{s};
+    }
+
+    if (s.front() == '*') {
+        s.erase(0, 1);
+    }
+
+    if (!s.empty() && s.back() == '*') {
+        s.pop_back();
+    }
+
+    return osmium::StringMatcher::substring{s};
+}
+
+void CommandTagsFilter::parse_and_add_expression(const std::string& expression) {
+    const auto p = get_filter_expression(expression);
+    std::string key = p.second;
+
+    const auto op_pos = key.find('=');
+    if (op_pos == std::string::npos) {
+        add_filter(p.first, osmium::TagMatcher{get_matcher(key)});
+        return;
+    }
+
+    const auto value = key.substr(op_pos + 1);
+    key.erase(op_pos);
+
+    bool invert = false;
+    if (!key.empty() && key.back() == '!') {
+        key.pop_back();
+        invert = true;
+    }
+
+    add_filter(p.first, osmium::TagMatcher{get_matcher(key), get_matcher(value), invert});
+}
+
+void CommandTagsFilter::read_expressions_file(const std::string& file_name) {
+    m_vout << "Reading expressions file...\n";
+
+    std::ifstream file{file_name};
+    if (!file.is_open()) {
+        throw argument_error{"Could not open file '" + file_name + "'"};
+    }
+
+    for (std::string line; std::getline(file, line); ) {
+        const auto pos = line.find_first_of('#');
+        if (pos != std::string::npos) {
+            line.erase(pos);
+        }
+        if (!line.empty()) {
+            if (line.back() == '\r') {
+                line.resize(line.size() - 1);
+            }
+            parse_and_add_expression(line);
+        }
+    }
+}
+
+bool CommandTagsFilter::setup(const std::vector<std::string>& arguments) {
+    po::options_description opts_cmd{"COMMAND OPTIONS"};
+    opts_cmd.add_options()
+    ("expressions,e", po::value<std::string>(), "Read filter expressions from file")
+    ("invert-match,i", "Invert the sense of matching, exclude objects with matching tags")
+    ("omit-referenced,R", "Omit referenced objects")
+    ;
+
+    po::options_description opts_common{add_common_options()};
+    po::options_description opts_input{add_single_input_options()};
+    po::options_description opts_output{add_output_options()};
+
+    po::options_description hidden;
+    hidden.add_options()
+    ("input-filename", po::value<std::string>(), "OSM input file")
+    ("expression-list", po::value<std::vector<std::string>>(), "Filter expressions")
+    ;
+
+    po::options_description desc;
+    desc.add(opts_cmd).add(opts_common).add(opts_input).add(opts_output);
+
+    po::options_description parsed_options;
+    parsed_options.add(desc).add(hidden);
+
+    po::positional_options_description positional;
+    positional.add("input-filename", 1);
+    positional.add("expression-list", -1);
+
+    po::variables_map vm;
+    po::store(po::command_line_parser(arguments).options(parsed_options).positional(positional).run(), vm);
+    po::notify(vm);
+
+    setup_common(vm, desc);
+    setup_progress(vm);
+    setup_input_file(vm);
+    setup_output_file(vm);
+
+    if (vm.count("omit-referenced")) {
+        m_add_referenced_objects = false;
+    } else if (m_input_filename == "-") {
+        throw argument_error{"Can not read OSM input from STDIN (unless --omit-referenced/-R option is used)."};
+    }
+
+    if (vm.count("invert-match")) {
+        m_invert_match = true;
+    }
+
+    if (vm.count("expression-list")) {
+        for (const auto& e : vm["expression-list"].as<std::vector<std::string>>()) {
+            parse_and_add_expression(e);
+        }
+    }
+
+    if (vm.count("expressions")) {
+        read_expressions_file(vm["expressions"].as<std::string>());
+    }
+
+    return true;
+}
+
+void CommandTagsFilter::show_arguments() {
+    show_single_input_arguments(m_vout);
+    show_output_arguments(m_vout);
+
+    m_vout << "  other options:\n";
+    m_vout << "    add referenced objects: " << yes_no(m_add_referenced_objects);
+    m_vout << "  looking for tags...\n";
+    m_vout << "    on nodes: "     << yes_no(!m_filters(osmium::item_type::node).empty());
+    m_vout << "    on ways: "      << yes_no(!m_filters(osmium::item_type::way).empty());
+    m_vout << "    on relations: " << yes_no(!m_filters(osmium::item_type::relation).empty());
+}
+
+osmium::osm_entity_bits::type CommandTagsFilter::get_needed_types() const {
+    osmium::osm_entity_bits::type types = osmium::osm_entity_bits::nothing;
+
+    if (! m_ids(osmium::item_type::node).empty() || !m_filters(osmium::item_type::node).empty()) {
+        types |= osmium::osm_entity_bits::node;
+    }
+    if (! m_ids(osmium::item_type::way).empty() || !m_filters(osmium::item_type::way).empty()) {
+        types |= osmium::osm_entity_bits::way;
+    }
+    if (! m_ids(osmium::item_type::relation).empty() || !m_filters(osmium::item_type::relation).empty()) {
+        types |= osmium::osm_entity_bits::relation;
+    }
+
+    return types;
+}
+
+void CommandTagsFilter::add_nodes(const osmium::Way& way) {
+    for (const auto& nr : way.nodes()) {
+        m_ids(osmium::item_type::node).set(nr.positive_ref());
+    }
+}
+
+void CommandTagsFilter::add_members(const osmium::Relation& relation) {
+    for (const auto& member : relation.members()) {
+        m_ids(member.type()).set(member.positive_ref());
+    }
+}
+
+void CommandTagsFilter::mark_rel_ids(const osmium::index::RelationsMapIndex& rel_in_rel, osmium::object_id_type parent_id) {
+   rel_in_rel.for_each(parent_id, [&](osmium::unsigned_object_id_type member_id) {
+        if (m_ids(osmium::item_type::relation).check_and_set(member_id)) {
+            mark_rel_ids(rel_in_rel, member_id);
+        }
+   });
+}
+
+bool CommandTagsFilter::find_relations_in_relations() {
+    const auto& filter = m_filters(osmium::item_type::relation);
+
+    m_vout << "  Reading input file to find relations in relations...\n";
+    osmium::index::RelationsMapStash stash;
+
+    osmium::io::Reader reader{m_input_file, osmium::osm_entity_bits::relation};
+    while (osmium::memory::Buffer buffer = reader.read()) {
+        for (const auto& relation : buffer.select<osmium::Relation>()) {
+            stash.add_members(relation);
+            if (osmium::tags::match_any_of(relation.tags(), filter) != m_invert_match) {
+                m_ids(osmium::item_type::relation).set(relation.positive_id());
+            }
+        }
+    }
+    reader.close();
+
+    if (stash.empty()) {
+        return false;
+    }
+
+    const auto rel_in_rel = stash.build_parent_to_member_index();
+    for (const osmium::unsigned_object_id_type id : m_ids(osmium::item_type::relation)) {
+        mark_rel_ids(rel_in_rel, id);
+    }
+
+    return true;
+}
+
+void CommandTagsFilter::find_nodes_and_ways_in_relations() {
+    m_vout << "  Reading input file to find nodes/ways in relations...\n";
+
+    osmium::io::Reader reader{m_input_file, osmium::osm_entity_bits::relation};
+    while (osmium::memory::Buffer buffer = reader.read()) {
+        for (const auto& relation : buffer.select<osmium::Relation>()) {
+            if (m_ids(osmium::item_type::relation).get(relation.positive_id())) {
+                for (const auto& member : relation.members()) {
+                    if (member.type() == osmium::item_type::node) {
+                        m_ids(osmium::item_type::node).set(member.positive_ref());
+                    } else if (member.type() == osmium::item_type::way) {
+                        m_ids(osmium::item_type::way).set(member.positive_ref());
+                    }
+                }
+            }
+        }
+    }
+    reader.close();
+}
+
+void CommandTagsFilter::find_nodes_in_ways() {
+    m_vout << "  Reading input file to find nodes in ways...\n";
+
+    osmium::io::Reader reader{m_input_file, osmium::osm_entity_bits::way};
+    while (osmium::memory::Buffer buffer = reader.read()) {
+        for (const auto& way : buffer.select<osmium::Way>()) {
+            if (m_ids(osmium::item_type::way).get(way.positive_id())) {
+                add_nodes(way);
+            } else if (osmium::tags::match_any_of(way.tags(), m_filters(osmium::item_type::way)) != m_invert_match) {
+                m_ids(osmium::item_type::way).set(way.positive_id());
+                add_nodes(way);
+            }
+        }
+    }
+    reader.close();
+}
+
+void CommandTagsFilter::find_referenced_objects() {
+    m_vout << "Following references...\n";
+    bool todo = !m_filters(osmium::item_type::relation).empty();
+    if (todo) {
+        todo = find_relations_in_relations();
+    }
+
+    if (todo) {
+        find_nodes_and_ways_in_relations();
+    }
+
+    if (!m_ids(osmium::item_type::way).empty() || !m_filters(osmium::item_type::way).empty()) {
+        find_nodes_in_ways();
+    }
+    m_vout << "Done following references.\n";
+}
+
+bool CommandTagsFilter::run() {
+    if (m_add_referenced_objects) {
+        find_referenced_objects();
+    }
+
+    m_vout << "Opening input file...\n";
+    osmium::io::Reader reader{m_input_file, get_needed_types()};
+
+    m_vout << "Opening output file...\n";
+    osmium::io::Header header = reader.header();
+    setup_header(header);
+
+    osmium::io::Writer writer{m_output_file, header, m_output_overwrite, m_fsync};
+
+    m_vout << "Copying matching objects to output file...\n";
+    osmium::ProgressBar progress_bar{reader.file_size(), display_progress()};
+    while (osmium::memory::Buffer buffer = reader.read()) {
+        progress_bar.update(reader.offset());
+        for (const auto& object : buffer.select<osmium::OSMObject>()) {
+            if (m_ids(object.type()).get(object.positive_id())) {
+                writer(object);
+            } else if (!m_add_referenced_objects || object.type() == osmium::item_type::node) {
+                const auto& filter = m_filters(object.type());
+                if (osmium::tags::match_any_of(object.tags(), filter) != m_invert_match) {
+                    writer(object);
+                }
+            }
+        }
+    }
+    progress_bar.done();
+
+    m_vout << "Closing output file...\n";
+    writer.close();
+
+    m_vout << "Closing input file...\n";
+    reader.close();
+
+    show_memory_used();
+
+    m_vout << "Done.\n";
+
+    return true;
+}
+
diff --git a/src/command_getid.hpp b/src/command_tags_filter.hpp
similarity index 58%
copy from src/command_getid.hpp
copy to src/command_tags_filter.hpp
index 44943c0..b35334f 100644
--- a/src/command_getid.hpp
+++ b/src/command_tags_filter.hpp
@@ -1,5 +1,5 @@
-#ifndef COMMAND_GETID_HPP
-#define COMMAND_GETID_HPP
+#ifndef COMMAND_TAGS_FILTER_HPP
+#define COMMAND_TAGS_FILTER_HPP
 
 /*
 
@@ -23,51 +23,51 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 */
 
-#include <map>
-#include <set>
 #include <string>
 #include <vector>
 
 #include <osmium/fwd.hpp>
 #include <osmium/index/id_set.hpp>
+#include <osmium/index/nwr_array.hpp>
 #include <osmium/osm/entity_bits.hpp>
 #include <osmium/osm/item_type.hpp>
 #include <osmium/osm/types.hpp>
+#include <osmium/tags/tags_filter.hpp>
 
 #include "cmd.hpp" // IWYU pragma: export
 
-class CommandGetId : public Command, public with_single_osm_input, public with_osm_output {
+namespace osmium { namespace index {
+    class RelationsMapIndex;
+}}
 
-    osmium::item_type m_default_item_type = osmium::item_type::node;
+class CommandTagsFilter : public Command, public with_single_osm_input, public with_osm_output {
 
-    bool m_add_referenced_objects = false;
-    bool m_work_with_history = false;
-    bool m_verbose_ids = false;
+    bool m_add_referenced_objects = true;
+    bool m_invert_match = false;
 
-    osmium::index::NWRIdSet<osmium::index::IdSetDense> m_ids;
+    osmium::nwr_array<osmium::TagsFilter> m_filters;
 
-    void parse_and_add_id(const std::string& s);
-
-    void read_id_osm_file(const std::string& file_name);
-    void read_id_file(std::istream& stream);
+    osmium::nwr_array<osmium::index::IdSetDense<osmium::unsigned_object_id_type>> m_ids;
 
     osmium::osm_entity_bits::type get_needed_types() const;
-    bool no_ids() const;
-    std::size_t count_ids() const;
 
     void find_referenced_objects();
 
     void add_nodes(const osmium::Way& way);
     void add_members(const osmium::Relation& relation);
 
-    void mark_rel_ids(const std::multimap<osmium::object_id_type, osmium::object_id_type>& rel_in_rel, osmium::object_id_type id);
+    void mark_rel_ids(const osmium::index::RelationsMapIndex& rel_in_rel, osmium::object_id_type id);
     bool find_relations_in_relations();
     void find_nodes_and_ways_in_relations();
     void find_nodes_in_ways();
 
+    void add_filter(osmium::osm_entity_bits::type entities, const osmium::TagMatcher& matcher);
+    void parse_and_add_expression(const std::string& expression);
+    void read_expressions_file(const std::string& file_name);
+
 public:
 
-    CommandGetId() = default;
+    CommandTagsFilter() = default;
 
     bool setup(const std::vector<std::string>& arguments) override final;
 
@@ -76,16 +76,15 @@ public:
     bool run() override final;
 
     const char* name() const noexcept override final {
-        return "getid";
+        return "tags-filter";
     }
 
     const char* synopsis() const noexcept override final {
-        return "osmium getid [OPTIONS] OSM-FILE ID...\n"
-               "       osmium getid [OPTIONS] OSM-FILE -i ID-FILE\n"
-               "       osmium getid [OPTIONS] OSM-FILE -I ID-OSM-FILE";
+        return "osmium tags-filter [OPTIONS] OSM-FILE FILTER-EXPRESSION...\n"
+               "       osmium tags-filter [OPTIONS] --expressions=FILE OSM-FILE";
     }
 
-}; // class CommandGetId
+}; // class CommandTagsFilter
 
 
-#endif // COMMAND_GETID_HPP
+#endif // COMMAND_TAGS_FILTER_HPP
diff --git a/src/command_time_filter.cpp b/src/command_time_filter.cpp
index 971978b..3edf370 100644
--- a/src/command_time_filter.cpp
+++ b/src/command_time_filter.cpp
@@ -23,6 +23,8 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <algorithm>
 #include <ctime>
 #include <stdexcept>
+#include <string>
+#include <vector>
 
 #include <boost/program_options.hpp>
 
diff --git a/src/commands.cpp b/src/commands.cpp
index 0bc9db6..916e59f 100644
--- a/src/commands.cpp
+++ b/src/commands.cpp
@@ -15,6 +15,7 @@
 #include "command_renumber.hpp"
 #include "command_show.hpp"
 #include "command_sort.hpp"
+#include "command_tags_filter.hpp"
 #include "command_time_filter.hpp"
 
 void register_commands() {
@@ -81,6 +82,10 @@ void register_commands() {
         return new CommandSort();
     });
 
+    CommandFactory::add("tags-filter", "Filter OSM data based on tags", []() {
+        return new CommandTagsFilter();
+    });
+
     CommandFactory::add("time-filter", "Filter OSM data from a point in time or a time span out of a history file", []() {
         return new CommandTimeFilter();
     });
diff --git a/src/extract/extract.hpp b/src/extract/extract.hpp
index 6a5bc10..cb846c0 100644
--- a/src/extract/extract.hpp
+++ b/src/extract/extract.hpp
@@ -25,6 +25,8 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 #include <memory>
 #include <string>
+#include <utility>
+#include <vector>
 
 #include <osmium/osm/box.hpp>
 #include <osmium/io/file.hpp>
@@ -48,6 +50,7 @@ class Extract {
 
     osmium::io::File m_output_file;
     std::string m_description;
+    std::vector<std::pair<std::string, std::string>> m_header_options;
     osmium::Box m_envelope;
     std::unique_ptr<osmium::io::Writer> m_writer;
 
@@ -78,6 +81,14 @@ public:
         return m_envelope;
     }
 
+    void add_header_option(const std::string& name, const std::string& value) {
+        m_header_options.emplace_back(name, value);
+    }
+
+    const std::vector<std::pair<std::string, std::string>>& header_options() const noexcept {
+        return m_header_options;
+    }
+
     osmium::io::Writer& writer() {
         return *m_writer;
     }
diff --git a/src/extract/geojson_file_parser.cpp b/src/extract/geojson_file_parser.cpp
index 6a8d8e4..e844979 100644
--- a/src/extract/geojson_file_parser.cpp
+++ b/src/extract/geojson_file_parser.cpp
@@ -129,12 +129,14 @@ std::size_t parse_multipolygon_array(const rapidjson::Value& value, osmium::memo
         throw config_error{"Multipolygon must contain at least one polygon array."};
     }
 
-    for (const auto& polygon : array) {
-        if (!polygon.IsArray()) {
-            throw config_error{"Polygon must be an array."};
-        }
+    {
         osmium::builder::AreaBuilder builder{buffer};
-        parse_rings(polygon, builder);
+        for (const auto& polygon : array) {
+            if (!polygon.IsArray()) {
+                throw config_error{"Polygon must be an array."};
+            }
+            parse_rings(polygon, builder);
+        }
     }
 
     return buffer.commit();
@@ -190,8 +192,8 @@ std::size_t GeoJSONFileParser::operator()() {
     if (geometry_type.empty()) {
         error("Missing 'geometry.type'.");
     }
-    if (geometry_type != "Polygon" && geometry_type != "Multipolygon") {
-        error("Expected 'geometry.type' value to be 'Polygon' or 'Multipolygon'.");
+    if (geometry_type != "Polygon" && geometry_type != "MultiPolygon") {
+        error("Expected 'geometry.type' value to be 'Polygon' or 'MultiPolygon'.");
     }
 
     const auto json_coordinates = json_geometry->value.FindMember("coordinates");
diff --git a/src/extract/strategy_complete_ways.cpp b/src/extract/strategy_complete_ways.cpp
index bb70fb4..8dad999 100644
--- a/src/extract/strategy_complete_ways.cpp
+++ b/src/extract/strategy_complete_ways.cpp
@@ -24,6 +24,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <osmium/util/file.hpp>
 
 #include "strategy_complete_ways.hpp"
+#include "../util.hpp"
 
 namespace strategy_complete_ways {
 
@@ -36,12 +37,16 @@ namespace strategy_complete_ways {
         });
     }
 
-    Strategy::Strategy(const std::vector<std::unique_ptr<Extract>>& extracts, const osmium::util::Options& /*options*/) :
+    Strategy::Strategy(const std::vector<std::unique_ptr<Extract>>& extracts, const osmium::util::Options& options) :
         ExtractStrategy() {
         m_extracts.reserve(extracts.size());
         for (const auto& extract : extracts) {
             m_extracts.emplace_back(*extract);
         }
+
+        for (const auto& option : options) {
+            warning(std::string{"Ignoring unknown option '"} + option.first + "' for 'complete_ways' strategy.\n");
+        }
     }
 
     const char* Strategy::name() const noexcept {
@@ -157,7 +162,7 @@ namespace strategy_complete_ways {
         progress_bar.file_done(file_size);
 
         // recursively get parents of all relations that are in an extract
-        auto relations_map = pass1.relations_map_stash().build_index();
+        const auto relations_map = pass1.relations_map_stash().build_member_to_parent_index();
         for (auto& e : m_extracts) {
             for (osmium::unsigned_object_id_type id : e.relation_ids) {
                 e.add_relation_parents(id, relations_map);
diff --git a/src/extract/strategy_complete_ways_with_history.cpp b/src/extract/strategy_complete_ways_with_history.cpp
index 8b4eccf..44663b2 100644
--- a/src/extract/strategy_complete_ways_with_history.cpp
+++ b/src/extract/strategy_complete_ways_with_history.cpp
@@ -169,7 +169,7 @@ namespace strategy_complete_ways_with_history {
         pass1.add_extra_nodes();
 
         // recursively get parents of all relations that are in an extract
-        auto relations_map = pass1.relations_map_stash().build_index();
+        const auto relations_map = pass1.relations_map_stash().build_member_to_parent_index();
         for (auto& e : m_extracts) {
             for (osmium::unsigned_object_id_type id : e.relation_ids) {
                 e.add_relation_parents(id, relations_map);
diff --git a/src/extract/strategy_simple.cpp b/src/extract/strategy_simple.cpp
index 9e4d8b5..477ca95 100644
--- a/src/extract/strategy_simple.cpp
+++ b/src/extract/strategy_simple.cpp
@@ -23,15 +23,20 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <osmium/handler/check_order.hpp>
 
 #include "strategy_simple.hpp"
+#include "../util.hpp"
 
 namespace strategy_simple {
 
-    Strategy::Strategy(const std::vector<std::unique_ptr<Extract>>& extracts, const osmium::util::Options& /*options*/) :
+    Strategy::Strategy(const std::vector<std::unique_ptr<Extract>>& extracts, const osmium::util::Options& options) :
         ExtractStrategy() {
         m_extracts.reserve(extracts.size());
         for (const auto& extract : extracts) {
             m_extracts.emplace_back(*extract);
         }
+
+        for (const auto& option : options) {
+            warning(std::string{"Ignoring unknown option '"} + option.first + "' for 'simple' strategy.\n");
+        }
     }
 
     const char* Strategy::name() const noexcept {
diff --git a/src/extract/strategy_smart.cpp b/src/extract/strategy_smart.cpp
index f3f63da..dba3021 100644
--- a/src/extract/strategy_smart.cpp
+++ b/src/extract/strategy_smart.cpp
@@ -24,6 +24,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <osmium/util/string.hpp>
 
 #include "strategy_smart.hpp"
+#include "../util.hpp"
 
 namespace strategy_smart {
 
@@ -61,6 +62,12 @@ namespace strategy_smart {
             m_extracts.emplace_back(*extract);
         }
 
+        for (const auto& option : options) {
+            if (std::string{"types"} != option.first) {
+                warning(std::string{"Ignoring unknown option '"} + option.first + "' for 'smart' strategy.\n");
+            }
+        }
+
         const auto types = options.get("types");
         if (types == "") {
             m_types = {"multipolygon"};
@@ -230,7 +237,7 @@ namespace strategy_smart {
         progress_bar.file_done(file_size);
 
         // recursively get parents of all relations that are in an extract
-        auto relations_map = pass1.relations_map_stash().build_index();
+        const auto relations_map = pass1.relations_map_stash().build_member_to_parent_index();
         for (auto& e : m_extracts) {
             for (osmium::unsigned_object_id_type id : e.relation_ids) {
                 e.add_relation_parents(id, relations_map);
diff --git a/src/io.cpp b/src/io.cpp
index 7c2b4fe..6d3ff03 100644
--- a/src/io.cpp
+++ b/src/io.cpp
@@ -164,7 +164,7 @@ void with_osm_output::check_output_file() {
         }
         if (m_output_filename == "") {
             throw argument_error{"Missing output file. Set the output file with --output/-o and/or\n"
-                                 "add the --input-format/-F option to specify the file format."};
+                                 "add the --output-format/-f option to specify the file format."};
         }
     }
 
diff --git a/src/util.cpp b/src/util.cpp
index 3f36bde..7ec8f22 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -27,6 +27,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 #include <osmium/io/file.hpp>
 #include <osmium/util/file.hpp>
 
+#include "exception.hpp"
 #include "util.hpp"
 
 /**
@@ -57,6 +58,10 @@ void warning(const char* text) {
     std::cerr << "WARNING: " << text;
 }
 
+void warning(const std::string& text) {
+    std::cerr << "WARNING: " << text;
+}
+
 std::size_t file_size_sum(const std::vector<osmium::io::File>& files) {
     std::size_t sum = 0;
 
@@ -67,3 +72,41 @@ std::size_t file_size_sum(const std::vector<osmium::io::File>& files) {
     return sum;
 }
 
+osmium::osm_entity_bits::type get_types(const std::string& str) {
+    osmium::osm_entity_bits::type entities{osmium::osm_entity_bits::nothing};
+
+    for (const auto c : str) {
+        switch (c) {
+            case 'n':
+                entities |= osmium::osm_entity_bits::node;
+                break;
+            case 'w':
+                entities |= osmium::osm_entity_bits::way;
+                break;
+            case 'r':
+                entities |= osmium::osm_entity_bits::relation;
+                break;
+            default:
+                throw argument_error{std::string{"Unknown object type '"} + c + "' (allowed are 'n', 'w', and 'r')."};
+        }
+    }
+
+    return entities;
+}
+
+std::pair<osmium::osm_entity_bits::type, std::string> get_filter_expression(const std::string& str) {
+    auto pos = str.find('/');
+
+    osmium::osm_entity_bits::type entities{osmium::osm_entity_bits::nwr};
+    if (pos == std::string::npos) {
+        pos = 0;
+    } else if (pos == 0) {
+        pos = 1;
+    } else {
+        entities = get_types(str.substr(0, pos));
+        ++pos;
+    }
+
+    return std::make_pair(entities, &str[pos]);
+}
+
diff --git a/src/util.hpp b/src/util.hpp
index 70295ab..318cf90 100644
--- a/src/util.hpp
+++ b/src/util.hpp
@@ -24,8 +24,11 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
 #include <string>
+#include <utility>
 #include <vector>
 
+#include <osmium/osm/entity_bits.hpp>
+
 namespace osmium { namespace io {
     class File;
 }}
@@ -33,6 +36,9 @@ namespace osmium { namespace io {
 std::string get_filename_suffix(const std::string& file_name);
 const char* yes_no(bool choice) noexcept;
 void warning(const char* text);
+void warning(const std::string& text);
 std::size_t file_size_sum(const std::vector<osmium::io::File>& files);
+osmium::osm_entity_bits::type get_types(const std::string& s);
+std::pair<osmium::osm_entity_bits::type, std::string> get_filter_expression(const std::string& s);
 
 #endif // UTIL_HPP
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 63940e0..08e75e1 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -15,6 +15,7 @@ file(GLOB ALL_UNIT_TESTS */test_setup.cpp */test_unit.cpp)
 file(GLOB ALL_COMMANDS ../src/command_*.cpp ../src/io.cpp ../src/cmd.cpp ../src/cmd_factory.cpp ../src/util.cpp ../src/extract/*.cpp)
 add_executable(unit_tests unit_tests.cpp ${ALL_COMMANDS} ${ALL_UNIT_TESTS} ${PROJECT_BINARY_DIR}/src/version.cpp)
 target_link_libraries(unit_tests ${Boost_LIBRARIES} ${OSMIUM_LIBRARIES})
+set_pthread_on_target(unit_tests)
 add_test(NAME unit_tests COMMAND unit_tests WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}")
 
 
@@ -81,6 +82,7 @@ check_cmd_help(changeset-filter)
 check_cmd_help(check-refs)
 check_cmd_help(derive-changes)
 check_cmd_help(diff)
+check_cmd_help(extract)
 check_cmd_help(fileinfo)
 check_cmd_help(getid)
 check_cmd_help(merge)
@@ -88,6 +90,7 @@ check_cmd_help(merge-changes)
 check_cmd_help(renumber)
 check_cmd_help(show)
 check_cmd_help(sort)
+check_cmd_help(tags-filter)
 check_cmd_help(time-filter)
 
 
diff --git a/test/apply-changes/CMakeLists.txt b/test/apply-changes/CMakeLists.txt
index ea0f57d..67d323e 100644
--- a/test/apply-changes/CMakeLists.txt
+++ b/test/apply-changes/CMakeLists.txt
@@ -23,5 +23,6 @@ check_apply_changes(history-osh-osh-wh "--with-history" input-history.osh input-
 check_apply_changes(history-osm-osh-wh "--with-history" input-history.osm input-change.osc "osh" output-history.osh)
 check_apply_changes(history-osh-osm-wh "--with-history" input-history.osh input-change.osc "osm" output-history.osh)
 
+check_apply_changes(data-low "--locations-on-ways" input-data-low.osm input-change.osc "osm" output-data-low.osm)
 
 #-----------------------------------------------------------------------------
diff --git a/test/apply-changes/input-data-low.osm b/test/apply-changes/input-data-low.osm
new file mode 100644
index 0000000..7862d80
--- /dev/null
+++ b/test/apply-changes/input-data-low.osm
@@ -0,0 +1,22 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" upload="false" generator="testdata">
+  <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
+  <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
+  <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="1"/>
+  <node id="13" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
+  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="10" lat="1" lon="1"/>
+    <nd ref="11" lat="2" lon="1"/>
+    <nd ref="12" lat="3" lon="1"/>
+    <tag k="foo" v="bar"/>
+  </way>
+  <way id="21" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="12" lat="3" lon="1"/>
+    <nd ref="13" lat="4" lon="1"/>
+    <tag k="xyz" v="abc"/>
+  </way>
+  <relation id="30" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="node" ref="12" role="m1"/>
+    <member type="way" ref="20" role="m2"/>
+  </relation>
+</osm>
diff --git a/test/apply-changes/output-data-low.osm b/test/apply-changes/output-data-low.osm
new file mode 100644
index 0000000..10cde4c
--- /dev/null
+++ b/test/apply-changes/output-data-low.osm
@@ -0,0 +1,22 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="test">
+  <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
+  <node id="11" version="2" timestamp="2015-01-01T02:00:00Z" uid="1" user="test" changeset="2" lat="2" lon="2"/>
+  <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="1"/>
+  <node id="14" version="1" timestamp="2015-01-01T02:00:00Z" uid="1" user="test" changeset="2" lat="5" lon="1"/>
+  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="10" lat="1" lon="1"/>
+    <nd ref="11" lat="2" lon="2"/>
+    <nd ref="12" lat="3" lon="1"/>
+    <tag k="foo" v="bar"/>
+  </way>
+  <way id="21" version="2" timestamp="2015-01-01T02:00:00Z" uid="1" user="test" changeset="2">
+    <nd ref="12" lat="3" lon="1"/>
+    <nd ref="14" lat="5" lon="1"/>
+    <tag k="xyz" v="new"/>
+  </way>
+  <relation id="30" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="node" ref="12" role="m1"/>
+    <member type="way" ref="20" role="m2"/>
+  </relation>
+</osm>
diff --git a/test/extract/CMakeLists.txt b/test/extract/CMakeLists.txt
new file mode 100644
index 0000000..8c550f6
--- /dev/null
+++ b/test/extract/CMakeLists.txt
@@ -0,0 +1,24 @@
+#-----------------------------------------------------------------------------
+#
+#  CMake Config
+#
+#  Osmium Tool Tests - extract
+#
+#-----------------------------------------------------------------------------
+
+function(check_extract _name _input _output _opts)
+    check_output(extract ${_name} "extract --generator=test -f osm extract/${_input} ${_opts} -b 0,0,1.5,10" "extract/${_output}")
+endfunction()
+
+
+#-----------------------------------------------------------------------------
+
+check_extract(simple        input1.osm output-simple.osm "-s simple")
+check_extract(complete_ways input1.osm output-complete-ways.osm "-s complete_ways")
+check_extract(smart_default input1.osm output-smart.osm "-s smart")
+check_extract(smart_mp      input1.osm output-smart.osm "-s smart -S types=multipolygon")
+check_extract(smart_any     input1.osm output-smart.osm "-s smart -S types=any")
+check_extract(smart_nonmp   input1.osm output-smart-nonmp.osm "-s smart -S types=x")
+
+
+#-----------------------------------------------------------------------------
diff --git a/test/extract/input1.osm b/test/extract/input1.osm
new file mode 100644
index 0000000..2effd93
--- /dev/null
+++ b/test/extract/input1.osm
@@ -0,0 +1,41 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" upload="false" generator="testdata">
+  <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="0" lon="1"/>
+  <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
+  <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
+  <node id="13" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="2"/>
+  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="2"/>
+  <node id="15" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="5" lon="2"/>
+  <node id="16" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="6" lon="2"/>
+  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="11"/>
+    <nd ref="12"/>
+    <nd ref="13"/>
+    <tag k="foo" v="bar"/>
+  </way>
+  <way id="21" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="14"/>
+    <nd ref="15"/>
+    <tag k="xyz" v="abc"/>
+  </way>
+  <relation id="31" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="node" ref="10" role=""/>
+  </relation>
+  <relation id="32" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="node" ref="13" role=""/>
+  </relation>
+  <relation id="33" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="way" ref="20" role=""/>
+  </relation>
+  <relation id="34" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="way" ref="20" role=""/>
+    <member type="way" ref="21" role=""/>
+    <tag k="type" v="multipolygon"/>
+  </relation>
+  <relation id="35" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="relation" ref="31" role=""/>
+  </relation>
+  <relation id="36" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="relation" ref="35" role=""/>
+  </relation>
+</osm>
diff --git a/test/extract/output-complete-ways.osm b/test/extract/output-complete-ways.osm
new file mode 100644
index 0000000..b8750c6
--- /dev/null
+++ b/test/extract/output-complete-ways.osm
@@ -0,0 +1,30 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="test">
+  <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="0" lon="1"/>
+  <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
+  <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
+  <node id="13" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="2"/>
+  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="11"/>
+    <nd ref="12"/>
+    <nd ref="13"/>
+    <tag k="foo" v="bar"/>
+  </way>
+  <relation id="31" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="node" ref="10" role=""/>
+  </relation>
+  <relation id="33" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="way" ref="20" role=""/>
+  </relation>
+  <relation id="34" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="way" ref="20" role=""/>
+    <member type="way" ref="21" role=""/>
+    <tag k="type" v="multipolygon"/>
+  </relation>
+  <relation id="35" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="relation" ref="31" role=""/>
+  </relation>
+  <relation id="36" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="relation" ref="35" role=""/>
+  </relation>
+</osm>
diff --git a/test/extract/output-simple.osm b/test/extract/output-simple.osm
new file mode 100644
index 0000000..fb4e7ab
--- /dev/null
+++ b/test/extract/output-simple.osm
@@ -0,0 +1,23 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="test">
+  <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="0" lon="1"/>
+  <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
+  <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
+  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="11"/>
+    <nd ref="12"/>
+    <nd ref="13"/>
+    <tag k="foo" v="bar"/>
+  </way>
+  <relation id="31" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="node" ref="10" role=""/>
+  </relation>
+  <relation id="33" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="way" ref="20" role=""/>
+  </relation>
+  <relation id="34" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="way" ref="20" role=""/>
+    <member type="way" ref="21" role=""/>
+    <tag k="type" v="multipolygon"/>
+  </relation>
+</osm>
diff --git a/test/extract/output-smart-nonmp.osm b/test/extract/output-smart-nonmp.osm
new file mode 100644
index 0000000..b8750c6
--- /dev/null
+++ b/test/extract/output-smart-nonmp.osm
@@ -0,0 +1,30 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="test">
+  <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="0" lon="1"/>
+  <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
+  <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
+  <node id="13" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="2"/>
+  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="11"/>
+    <nd ref="12"/>
+    <nd ref="13"/>
+    <tag k="foo" v="bar"/>
+  </way>
+  <relation id="31" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="node" ref="10" role=""/>
+  </relation>
+  <relation id="33" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="way" ref="20" role=""/>
+  </relation>
+  <relation id="34" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="way" ref="20" role=""/>
+    <member type="way" ref="21" role=""/>
+    <tag k="type" v="multipolygon"/>
+  </relation>
+  <relation id="35" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="relation" ref="31" role=""/>
+  </relation>
+  <relation id="36" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="relation" ref="35" role=""/>
+  </relation>
+</osm>
diff --git a/test/extract/output-smart.osm b/test/extract/output-smart.osm
new file mode 100644
index 0000000..53c9bbd
--- /dev/null
+++ b/test/extract/output-smart.osm
@@ -0,0 +1,37 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="test">
+  <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="0" lon="1"/>
+  <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
+  <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
+  <node id="13" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="2"/>
+  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="2"/>
+  <node id="15" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="5" lon="2"/>
+  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="11"/>
+    <nd ref="12"/>
+    <nd ref="13"/>
+    <tag k="foo" v="bar"/>
+  </way>
+  <way id="21" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="14"/>
+    <nd ref="15"/>
+    <tag k="xyz" v="abc"/>
+  </way>
+  <relation id="31" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="node" ref="10" role=""/>
+  </relation>
+  <relation id="33" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="way" ref="20" role=""/>
+  </relation>
+  <relation id="34" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="way" ref="20" role=""/>
+    <member type="way" ref="21" role=""/>
+    <tag k="type" v="multipolygon"/>
+  </relation>
+  <relation id="35" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="relation" ref="31" role=""/>
+  </relation>
+  <relation id="36" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="relation" ref="35" role=""/>
+  </relation>
+</osm>
diff --git a/test/extract/test_unit.cpp b/test/extract/test_unit.cpp
index 01d18d3..6957164 100644
--- a/test/extract/test_unit.cpp
+++ b/test/extract/test_unit.cpp
@@ -12,45 +12,32 @@ TEST_CASE("Parse poly files") {
     osmium::memory::Buffer buffer{1024};
 
     SECTION("Missing file") {
-        REQUIRE_THROWS({
-            PolyFileParser parser(buffer, "test/extract/missing.poly");
-            parser();
-        });
+        REQUIRE_THROWS(PolyFileParser(buffer, "test/extract/missing.poly")());
     }
 
     SECTION("Empty file") {
         PolyFileParser parser{buffer, "test/extract/empty.poly"};
-        REQUIRE_THROWS_AS({
-            parser();
-        }, poly_error);
+        REQUIRE_THROWS_AS(parser(), poly_error);
     }
 
     SECTION("One line file") {
         PolyFileParser parser{buffer, "test/extract/one-line.poly"};
-        REQUIRE_THROWS_AS({
-            parser();
-        }, poly_error);
+        REQUIRE_THROWS_AS(parser(), poly_error);
     }
 
     SECTION("Two line file") {
         PolyFileParser parser{buffer, "test/extract/two-line.poly"};
-        REQUIRE_THROWS_AS({
-            parser();
-        }, poly_error);
+        REQUIRE_THROWS_AS(parser(), poly_error);
     }
 
     SECTION("Missing END ring") {
         PolyFileParser parser{buffer, "test/extract/missing-end-ring.poly"};
-        REQUIRE_THROWS_AS({
-            parser();
-        }, poly_error);
+        REQUIRE_THROWS_AS(parser(), poly_error);
     }
 
     SECTION("Missing END polygon") {
         PolyFileParser parser{buffer, "test/extract/missing-end-polygon.poly"};
-        REQUIRE_THROWS_AS({
-            parser();
-        }, poly_error);
+        REQUIRE_THROWS_AS(parser(), poly_error);
     }
 
     SECTION("File with one polygon with one outer ring") {
@@ -138,24 +125,17 @@ TEST_CASE("Parse OSM files") {
     osmium::memory::Buffer buffer{1024};
 
     SECTION("Missing OSM file") {
-        REQUIRE_THROWS({
-            OSMFileParser parser(buffer, "test/extract/missing.osm.opl");
-            parser();
-        });
+        REQUIRE_THROWS(OSMFileParser(buffer, "test/extract/missing.osm.opl")());
     }
 
     SECTION("Empty OSM file") {
         OSMFileParser parser{buffer, "test/extract/empty.osm.opl"};
-        REQUIRE_THROWS({
-            parser();
-        });
+        REQUIRE_THROWS(parser());
     }
 
     SECTION("OSM file without polygon") {
         OSMFileParser parser{buffer, "test/extract/no-polygon.osm.opl"};
-        REQUIRE_THROWS({
-            parser();
-        });
+        REQUIRE_THROWS(parser());
     }
 
     SECTION("OSM file with simple polygon") {
@@ -232,45 +212,32 @@ TEST_CASE("Parse GeoJSON files") {
     osmium::memory::Buffer buffer{1024};
 
     SECTION("Missing GeoJSON file") {
-        REQUIRE_THROWS({
-            GeoJSONFileParser parser(buffer, "test/extract/missing.geojson");
-            parser();
-        });
+        REQUIRE_THROWS(GeoJSONFileParser(buffer, "test/extract/missing.geojson")());
     }
 
     SECTION("Empty GeoJSON file") {
         GeoJSONFileParser parser{buffer, "test/extract/empty.geojson"};
-        REQUIRE_THROWS({
-            parser();
-        });
+        REQUIRE_THROWS(parser());
     }
 
     SECTION("Invalid GeoJSON file") {
         GeoJSONFileParser parser{buffer, "test/extract/invalid.geojson"};
-        REQUIRE_THROWS_AS({
-            parser();
-        }, geojson_error);
+        REQUIRE_THROWS_AS(parser(), geojson_error);
     }
 
     SECTION("Invalid GeoJSON file: Root not an object") {
         GeoJSONFileParser parser{buffer, "test/extract/invalid-root.geojson"};
-        REQUIRE_THROWS_AS({
-            parser();
-        }, geojson_error);
+        REQUIRE_THROWS_AS(parser(), geojson_error);
     }
 
     SECTION("Invalid GeoJSON file: Empty root object") {
         GeoJSONFileParser parser{buffer, "test/extract/empty-root.geojson"};
-        REQUIRE_THROWS_AS({
-            parser();
-        }, geojson_error);
+        REQUIRE_THROWS_AS(parser(), geojson_error);
     }
 
     SECTION("Invalid GeoJSON file: Wrong geometry type") {
         GeoJSONFileParser parser{buffer, "test/extract/wrong-geometry-type.geojson"};
-        REQUIRE_THROWS_AS({
-            parser();
-        }, geojson_error);
+        REQUIRE_THROWS_AS(parser(), geojson_error);
     }
 
 }
diff --git a/test/include/catch.hpp b/test/include/catch.hpp
index 3d18ead..6f9334b 100644
--- a/test/include/catch.hpp
+++ b/test/include/catch.hpp
@@ -1,6 +1,6 @@
 /*
- *  Catch v1.5.9
- *  Generated: 2016-11-29 12:14:38.049276
+ *  Catch v1.8.1
+ *  Generated: 2017-03-01 16:04:19.016511
  *  ----------------------------------------------------------
  *  This file has been merged from multiple headers. Please don't edit it directly
  *  Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved.
@@ -40,6 +40,12 @@
 #elif defined __GNUC__
 #    pragma GCC diagnostic ignored "-Wvariadic-macros"
 #    pragma GCC diagnostic ignored "-Wunused-variable"
+
+     // For newer version we can use __Pragma to disable the warnings locally
+#    if __GNUC__ == 4 && __GNUC_MINOR__ >= 4 && __GNUC_MINOR__ <= 7
+#        pragma GCC diagnostic ignored "-Wparentheses"
+#    endif
+
 #    pragma GCC diagnostic push
 #    pragma GCC diagnostic ignored "-Wpadded"
 #endif
@@ -60,21 +66,6 @@
 // #included from: catch_common.h
 #define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED
 
-#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line
-#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line )
-#ifdef CATCH_CONFIG_COUNTER
-#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ )
-#else
-#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ )
-#endif
-
-#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr
-#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr )
-
-#include <sstream>
-#include <stdexcept>
-#include <algorithm>
-
 // #included from: catch_compiler_capabilities.h
 #define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED
 
@@ -89,11 +80,15 @@
 // CATCH_CONFIG_CPP11_LONG_LONG : is long long supported?
 // CATCH_CONFIG_CPP11_OVERRIDE : is override supported?
 // CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr)
+// CATCH_CONFIG_CPP11_SHUFFLE : is std::shuffle supported?
+// CATCH_CONFIG_CPP11_TYPE_TRAITS : are type_traits and enable_if supported?
 
 // CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported?
 
 // CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported?
 // CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported?
+// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported?
+// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported?
 // ****************
 // Note to maintainers: if new toggles are added please document them
 // in configuration.md, too
@@ -129,12 +124,30 @@
 #  endif
 
 #   if defined(CATCH_CPP11_OR_GREATER)
-#       define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "clang diagnostic ignored \"-Wparentheses\"" )
+#       define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
+            _Pragma( "clang diagnostic push" ) \
+            _Pragma( "clang diagnostic ignored \"-Wparentheses\"" )
+#       define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
+            _Pragma( "clang diagnostic pop" )
 #   endif
 
 #endif // __clang__
 
 ////////////////////////////////////////////////////////////////////////////////
+// Cygwin
+#ifdef __CYGWIN__
+
+#   if !defined(CATCH_CONFIG_POSIX_SIGNALS)
+#       define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
+#   endif
+
+// Required for some versions of Cygwin to declare gettimeofday
+// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin
+#   define _BSD_SOURCE
+
+#endif // __CYGWIN__
+
+////////////////////////////////////////////////////////////////////////////////
 // Borland
 #ifdef __BORLANDC__
 
@@ -156,12 +169,20 @@
 // GCC
 #ifdef __GNUC__
 
+#   if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
+#       define CATCH_GCC_HAS_NEW_PRAGMA
+#   endif
+
 #   if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__)
 #       define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
 #   endif
 
-#   if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) && defined(CATCH_CPP11_OR_GREATER)
-#       define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" )
+#   if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) && defined(CATCH_GCC_HAS_NEW_PRAGMA)
+#       define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
+            _Pragma( "GCC diagnostic push" ) \
+            _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" )
+#       define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
+            _Pragma( "GCC diagnostic pop" )
 #   endif
 
 // - otherwise more recent versions define __cplusplus >= 201103L
@@ -173,6 +194,8 @@
 // Visual C++
 #ifdef _MSC_VER
 
+#define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
+
 #if (_MSC_VER >= 1600)
 #   define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
 #   define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
@@ -181,6 +204,8 @@
 #if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015))
 #define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
 #define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE
+#define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS
 #endif
 
 #endif // _MSC_VER
@@ -246,6 +271,12 @@
 #  if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR)
 #    define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
 #  endif
+# if !defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE)
+#   define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE
+#  endif
+# if !defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS)
+#  define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS
+# endif
 
 #endif // __cplusplus >= 201103L
 
@@ -268,21 +299,38 @@
 #if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS)
 #   define CATCH_CONFIG_VARIADIC_MACROS
 #endif
-#if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11)
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11)
 #   define CATCH_CONFIG_CPP11_LONG_LONG
 #endif
-#if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11)
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11)
 #   define CATCH_CONFIG_CPP11_OVERRIDE
 #endif
-#if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11)
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11)
 #   define CATCH_CONFIG_CPP11_UNIQUE_PTR
 #endif
-#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER)
+// Use of __COUNTER__ is suppressed if __JETBRAINS_IDE__ is #defined (meaning we're being parsed by a JetBrains IDE for
+// analytics) because, at time of writing, __COUNTER__ is not properly handled by it.
+// This does not affect compilation
+#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) && !defined(__JETBRAINS_IDE__)
 #   define CATCH_CONFIG_COUNTER
 #endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_NO_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_NO_CPP11)
+#   define CATCH_CONFIG_CPP11_SHUFFLE
+#endif
+# if defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_NO_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_NO_CPP11)
+#  define CATCH_CONFIG_CPP11_TYPE_TRAITS
+# endif
+#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH)
+#   define CATCH_CONFIG_WINDOWS_SEH
+#endif
+// This is set by default, because we assume that unix compilers are posix-signal-compatible by default.
+#if !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS)
+#   define CATCH_CONFIG_POSIX_SIGNALS
+#endif
 
 #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS)
 #   define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS
+#   define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS
 #endif
 
 // noexcept support:
@@ -315,6 +363,20 @@
 #   define CATCH_AUTO_PTR( T ) std::auto_ptr<T>
 #endif
 
+#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line
+#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line )
+#ifdef CATCH_CONFIG_COUNTER
+#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ )
+#else
+#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ )
+#endif
+
+#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr
+#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr )
+
+#include <sstream>
+#include <algorithm>
+
 namespace Catch {
 
     struct IConfig;
@@ -367,7 +429,9 @@ namespace Catch {
     }
 
     bool startsWith( std::string const& s, std::string const& prefix );
+    bool startsWith( std::string const& s, char prefix );
     bool endsWith( std::string const& s, std::string const& suffix );
+    bool endsWith( std::string const& s, char suffix );
     bool contains( std::string const& s, std::string const& infix );
     void toLowerInPlace( std::string& s );
     std::string toLower( std::string const& s );
@@ -387,8 +451,8 @@ namespace Catch {
 
         SourceLineInfo();
         SourceLineInfo( char const* _file, std::size_t _line );
-        SourceLineInfo( SourceLineInfo const& other );
 #  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+        SourceLineInfo(SourceLineInfo const& other)          = default;
         SourceLineInfo( SourceLineInfo && )                  = default;
         SourceLineInfo& operator = ( SourceLineInfo const& ) = default;
         SourceLineInfo& operator = ( SourceLineInfo && )     = default;
@@ -397,7 +461,7 @@ namespace Catch {
         bool operator == ( SourceLineInfo const& other ) const;
         bool operator < ( SourceLineInfo const& other ) const;
 
-        std::string file;
+        char const* file;
         std::size_t line;
     };
 
@@ -431,8 +495,6 @@ namespace Catch {
 #define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) )
 #define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO );
 
-#include <ostream>
-
 namespace Catch {
 
     class NotImplementedException : public std::exception
@@ -565,10 +627,6 @@ namespace Catch {
 #pragma clang diagnostic pop
 #endif
 
-#include <memory>
-#include <vector>
-#include <stdlib.h>
-
 namespace Catch {
 
     class TestCase;
@@ -832,6 +890,30 @@ namespace Catch {
 
 namespace Catch {
 
+    struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison;
+
+    struct DecomposedExpression
+    {
+        virtual ~DecomposedExpression() {}
+        virtual bool isBinaryExpression() const {
+            return false;
+        }
+        virtual void reconstructExpression( std::string& dest ) const = 0;
+
+        // Only simple binary comparisons can be decomposed.
+        // If more complex check is required then wrap sub-expressions in parentheses.
+        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( T const& );
+        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( T const& );
+        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( T const& );
+        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( T const& );
+        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator % ( T const& );
+        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( T const& );
+        template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( T const& );
+
+	private:
+		DecomposedExpression& operator = (DecomposedExpression const&);
+    };
+
     struct AssertionInfo
     {
         AssertionInfo() {}
@@ -848,11 +930,41 @@ namespace Catch {
 
     struct AssertionResultData
     {
-        AssertionResultData() : resultType( ResultWas::Unknown ) {}
+        AssertionResultData() : decomposedExpression( CATCH_NULL )
+                              , resultType( ResultWas::Unknown )
+                              , negated( false )
+                              , parenthesized( false ) {}
+
+        void negate( bool parenthesize ) {
+            negated = !negated;
+            parenthesized = parenthesize;
+            if( resultType == ResultWas::Ok )
+                resultType = ResultWas::ExpressionFailed;
+            else if( resultType == ResultWas::ExpressionFailed )
+                resultType = ResultWas::Ok;
+        }
+
+        std::string const& reconstructExpression() const {
+            if( decomposedExpression != CATCH_NULL ) {
+                decomposedExpression->reconstructExpression( reconstructedExpression );
+                if( parenthesized ) {
+                    reconstructedExpression.insert( 0, 1, '(' );
+                    reconstructedExpression.append( 1, ')' );
+                }
+                if( negated ) {
+                    reconstructedExpression.insert( 0, 1, '!' );
+                }
+                decomposedExpression = CATCH_NULL;
+            }
+            return reconstructedExpression;
+        }
 
-        std::string reconstructedExpression;
+        mutable DecomposedExpression const* decomposedExpression;
+        mutable std::string reconstructedExpression;
         std::string message;
         ResultWas::OfType resultType;
+        bool negated;
+        bool parenthesized;
     };
 
     class AssertionResult {
@@ -879,6 +991,8 @@ namespace Catch {
         std::string getMessage() const;
         SourceLineInfo getSourceInfo() const;
         std::string getTestMacroName() const;
+        void discardDecomposedExpression() const;
+        void expandDecomposedExpression() const;
 
     protected:
         AssertionInfo m_info;
@@ -894,313 +1008,153 @@ namespace Catch {
 namespace Matchers {
     namespace Impl {
 
-    namespace Generic {
-        template<typename ExpressionT> class AllOf;
-        template<typename ExpressionT> class AnyOf;
-        template<typename ExpressionT> class Not;
-    }
-
-    template<typename ExpressionT>
-    struct Matcher : SharedImpl<IShared>
-    {
-        typedef ExpressionT ExpressionType;
-
-        virtual ~Matcher() {}
-        virtual Ptr<Matcher> clone() const = 0;
-        virtual bool match( ExpressionT const& expr ) const = 0;
-        virtual std::string toString() const = 0;
+        template<typename ArgT> struct MatchAllOf;
+        template<typename ArgT> struct MatchAnyOf;
+        template<typename ArgT> struct MatchNotOf;
 
-        Generic::AllOf<ExpressionT> operator && ( Matcher<ExpressionT> const& other ) const;
-        Generic::AnyOf<ExpressionT> operator || ( Matcher<ExpressionT> const& other ) const;
-        Generic::Not<ExpressionT> operator ! () const;
-    };
-
-    template<typename DerivedT, typename ExpressionT>
-    struct MatcherImpl : Matcher<ExpressionT> {
-
-        virtual Ptr<Matcher<ExpressionT> > clone() const {
-            return Ptr<Matcher<ExpressionT> >( new DerivedT( static_cast<DerivedT const&>( *this ) ) );
-        }
-    };
-
-    namespace Generic {
-        template<typename ExpressionT>
-        class Not : public MatcherImpl<Not<ExpressionT>, ExpressionT> {
+        class MatcherUntypedBase {
         public:
-            explicit Not( Matcher<ExpressionT> const& matcher ) : m_matcher(matcher.clone()) {}
-            Not( Not const& other ) : m_matcher( other.m_matcher ) {}
-
-            virtual bool match( ExpressionT const& expr ) const CATCH_OVERRIDE {
-                return !m_matcher->match( expr );
+            std::string toString() const {
+                if( m_cachedToString.empty() )
+                    m_cachedToString = describe();
+                return m_cachedToString;
             }
 
-            virtual std::string toString() const CATCH_OVERRIDE {
-                return "not " + m_matcher->toString();
-            }
+        protected:
+            virtual std::string describe() const = 0;
+            mutable std::string m_cachedToString;
         private:
-            Ptr< Matcher<ExpressionT> > m_matcher;
+            MatcherUntypedBase& operator = ( MatcherUntypedBase const& );
         };
 
-        template<typename ExpressionT>
-        class AllOf : public MatcherImpl<AllOf<ExpressionT>, ExpressionT> {
-        public:
+        template<typename ObjectT, typename ComparatorT = ObjectT>
+        struct MatcherBase : MatcherUntypedBase {
 
-            AllOf() {}
-            AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {}
+            virtual bool match( ObjectT const& arg ) const = 0;
 
-            AllOf& add( Matcher<ExpressionT> const& matcher ) {
-                m_matchers.push_back( matcher.clone() );
-                return *this;
-            }
-            virtual bool match( ExpressionT const& expr ) const
-            {
-                for( std::size_t i = 0; i < m_matchers.size(); ++i )
-                    if( !m_matchers[i]->match( expr ) )
+            MatchAllOf<ComparatorT> operator && ( MatcherBase const& other ) const;
+            MatchAnyOf<ComparatorT> operator || ( MatcherBase const& other ) const;
+            MatchNotOf<ComparatorT> operator ! () const;
+        };
+
+        template<typename ArgT>
+        struct MatchAllOf : MatcherBase<ArgT> {
+            virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE {
+                for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
+                    if (!m_matchers[i]->match(arg))
                         return false;
+                }
                 return true;
             }
-            virtual std::string toString() const {
-                std::ostringstream oss;
-                oss << "( ";
+            virtual std::string describe() const CATCH_OVERRIDE {
+                std::string description;
+                description.reserve( 4 + m_matchers.size()*32 );
+                description += "( ";
                 for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
                     if( i != 0 )
-                        oss << " and ";
-                    oss << m_matchers[i]->toString();
+                        description += " and ";
+                    description += m_matchers[i]->toString();
                 }
-                oss << " )";
-                return oss.str();
+                description += " )";
+                return description;
             }
 
-            AllOf operator && ( Matcher<ExpressionT> const& other ) const {
-                AllOf allOfExpr( *this );
-                allOfExpr.add( other );
-                return allOfExpr;
+            MatchAllOf<ArgT>& operator && ( MatcherBase<ArgT> const& other ) {
+                m_matchers.push_back( &other );
+                return *this;
             }
 
-        private:
-            std::vector<Ptr<Matcher<ExpressionT> > > m_matchers;
+            std::vector<MatcherBase<ArgT> const*> m_matchers;
         };
+        template<typename ArgT>
+        struct MatchAnyOf : MatcherBase<ArgT> {
 
-        template<typename ExpressionT>
-        class AnyOf : public MatcherImpl<AnyOf<ExpressionT>, ExpressionT> {
-        public:
-
-            AnyOf() {}
-            AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {}
-
-            AnyOf& add( Matcher<ExpressionT> const& matcher ) {
-                m_matchers.push_back( matcher.clone() );
-                return *this;
-            }
-            virtual bool match( ExpressionT const& expr ) const
-            {
-                for( std::size_t i = 0; i < m_matchers.size(); ++i )
-                    if( m_matchers[i]->match( expr ) )
+            virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE {
+                for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
+                    if (m_matchers[i]->match(arg))
                         return true;
+                }
                 return false;
             }
-            virtual std::string toString() const {
-                std::ostringstream oss;
-                oss << "( ";
+            virtual std::string describe() const CATCH_OVERRIDE {
+                std::string description;
+                description.reserve( 4 + m_matchers.size()*32 );
+                description += "( ";
                 for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
                     if( i != 0 )
-                        oss << " or ";
-                    oss << m_matchers[i]->toString();
+                        description += " or ";
+                    description += m_matchers[i]->toString();
                 }
-                oss << " )";
-                return oss.str();
+                description += " )";
+                return description;
             }
 
-            AnyOf operator || ( Matcher<ExpressionT> const& other ) const {
-                AnyOf anyOfExpr( *this );
-                anyOfExpr.add( other );
-                return anyOfExpr;
-            }
-
-        private:
-            std::vector<Ptr<Matcher<ExpressionT> > > m_matchers;
-        };
-
-    } // namespace Generic
-
-    template<typename ExpressionT>
-    Generic::AllOf<ExpressionT> Matcher<ExpressionT>::operator && ( Matcher<ExpressionT> const& other ) const {
-        Generic::AllOf<ExpressionT> allOfExpr;
-        allOfExpr.add( *this );
-        allOfExpr.add( other );
-        return allOfExpr;
-    }
-
-    template<typename ExpressionT>
-    Generic::AnyOf<ExpressionT> Matcher<ExpressionT>::operator || ( Matcher<ExpressionT> const& other ) const {
-        Generic::AnyOf<ExpressionT> anyOfExpr;
-        anyOfExpr.add( *this );
-        anyOfExpr.add( other );
-        return anyOfExpr;
-    }
-
-    template<typename ExpressionT>
-    Generic::Not<ExpressionT> Matcher<ExpressionT>::operator ! () const {
-        return Generic::Not<ExpressionT>( *this );
-    }
-
-    namespace StdString {
-
-        inline std::string makeString( std::string const& str ) { return str; }
-        inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); }
-
-        struct CasedString
-        {
-            CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity )
-            :   m_caseSensitivity( caseSensitivity ),
-                m_str( adjustString( str ) )
-            {}
-            std::string adjustString( std::string const& str ) const {
-                return m_caseSensitivity == CaseSensitive::No
-                    ? toLower( str )
-                    : str;
-
-            }
-            std::string toStringSuffix() const
-            {
-                return m_caseSensitivity == CaseSensitive::No
-                    ? " (case insensitive)"
-                    : "";
-            }
-            CaseSensitive::Choice m_caseSensitivity;
-            std::string m_str;
-        };
-
-        struct Equals : MatcherImpl<Equals, std::string> {
-            Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes )
-            :   m_data( str, caseSensitivity )
-            {}
-            Equals( Equals const& other ) : m_data( other.m_data ){}
-
-            virtual ~Equals();
-
-            virtual bool match( std::string const& expr ) const {
-                return m_data.m_str == m_data.adjustString( expr );;
-            }
-            virtual std::string toString() const {
-                return "equals: \"" + m_data.m_str + "\"" + m_data.toStringSuffix();
+            MatchAnyOf<ArgT>& operator || ( MatcherBase<ArgT> const& other ) {
+                m_matchers.push_back( &other );
+                return *this;
             }
 
-            CasedString m_data;
+            std::vector<MatcherBase<ArgT> const*> m_matchers;
         };
 
-        struct Contains : MatcherImpl<Contains, std::string> {
-            Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes )
-            : m_data( substr, caseSensitivity ){}
-            Contains( Contains const& other ) : m_data( other.m_data ){}
+        template<typename ArgT>
+        struct MatchNotOf : MatcherBase<ArgT> {
 
-            virtual ~Contains();
+            MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {}
 
-            virtual bool match( std::string const& expr ) const {
-                return m_data.adjustString( expr ).find( m_data.m_str ) != std::string::npos;
-            }
-            virtual std::string toString() const {
-                return "contains: \"" + m_data.m_str  + "\"" + m_data.toStringSuffix();
+            virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE {
+                return !m_underlyingMatcher.match( arg );
             }
 
-            CasedString m_data;
-        };
-
-        struct StartsWith : MatcherImpl<StartsWith, std::string> {
-            StartsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes )
-            : m_data( substr, caseSensitivity ){}
-
-            StartsWith( StartsWith const& other ) : m_data( other.m_data ){}
-
-            virtual ~StartsWith();
-
-            virtual bool match( std::string const& expr ) const {
-                return startsWith( m_data.adjustString( expr ), m_data.m_str );
-            }
-            virtual std::string toString() const {
-                return "starts with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix();
+            virtual std::string describe() const CATCH_OVERRIDE {
+                return "not " + m_underlyingMatcher.toString();
             }
-
-            CasedString m_data;
+            MatcherBase<ArgT> const& m_underlyingMatcher;
         };
 
-        struct EndsWith : MatcherImpl<EndsWith, std::string> {
-            EndsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes )
-            : m_data( substr, caseSensitivity ){}
-            EndsWith( EndsWith const& other ) : m_data( other.m_data ){}
-
-            virtual ~EndsWith();
-
-            virtual bool match( std::string const& expr ) const {
-                return endsWith( m_data.adjustString( expr ), m_data.m_str );
-            }
-            virtual std::string toString() const {
-                return "ends with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix();
-            }
+        template<typename ObjectT, typename ComparatorT>
+        MatchAllOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator && ( MatcherBase const& other ) const {
+            return MatchAllOf<ComparatorT>() && *this && other;
+        }
+        template<typename ObjectT, typename ComparatorT>
+        MatchAnyOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator || ( MatcherBase const& other ) const {
+            return MatchAnyOf<ComparatorT>() || *this || other;
+        }
+        template<typename ObjectT, typename ComparatorT>
+        MatchNotOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator ! () const {
+            return MatchNotOf<ComparatorT>( *this );
+        }
 
-            CasedString m_data;
-        };
-    } // namespace StdString
     } // namespace Impl
 
     // The following functions create the actual matcher objects.
     // This allows the types to be inferred
-    template<typename ExpressionT>
-    inline Impl::Generic::Not<ExpressionT> Not( Impl::Matcher<ExpressionT> const& m ) {
-        return Impl::Generic::Not<ExpressionT>( m );
-    }
-
-    template<typename ExpressionT>
-    inline Impl::Generic::AllOf<ExpressionT> AllOf( Impl::Matcher<ExpressionT> const& m1,
-                                                    Impl::Matcher<ExpressionT> const& m2 ) {
-        return Impl::Generic::AllOf<ExpressionT>().add( m1 ).add( m2 );
-    }
-    template<typename ExpressionT>
-    inline Impl::Generic::AllOf<ExpressionT> AllOf( Impl::Matcher<ExpressionT> const& m1,
-                                                    Impl::Matcher<ExpressionT> const& m2,
-                                                    Impl::Matcher<ExpressionT> const& m3 ) {
-        return Impl::Generic::AllOf<ExpressionT>().add( m1 ).add( m2 ).add( m3 );
-    }
-    template<typename ExpressionT>
-    inline Impl::Generic::AnyOf<ExpressionT> AnyOf( Impl::Matcher<ExpressionT> const& m1,
-                                                    Impl::Matcher<ExpressionT> const& m2 ) {
-        return Impl::Generic::AnyOf<ExpressionT>().add( m1 ).add( m2 );
-    }
-    template<typename ExpressionT>
-    inline Impl::Generic::AnyOf<ExpressionT> AnyOf( Impl::Matcher<ExpressionT> const& m1,
-                                                    Impl::Matcher<ExpressionT> const& m2,
-                                                    Impl::Matcher<ExpressionT> const& m3 ) {
-        return Impl::Generic::AnyOf<ExpressionT>().add( m1 ).add( m2 ).add( m3 );
-    }
-
-    inline Impl::StdString::Equals      Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) {
-        return Impl::StdString::Equals( str, caseSensitivity );
-    }
-    inline Impl::StdString::Equals      Equals( const char* str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) {
-        return Impl::StdString::Equals( Impl::StdString::makeString( str ), caseSensitivity );
-    }
-    inline Impl::StdString::Contains    Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) {
-        return Impl::StdString::Contains( substr, caseSensitivity );
-    }
-    inline Impl::StdString::Contains    Contains( const char* substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) {
-        return Impl::StdString::Contains( Impl::StdString::makeString( substr ), caseSensitivity );
+    // - deprecated: prefer ||, && and !
+    template<typename T>
+    inline Impl::MatchNotOf<T> Not( Impl::MatcherBase<T> const& underlyingMatcher ) {
+        return Impl::MatchNotOf<T>( underlyingMatcher );
     }
-    inline Impl::StdString::StartsWith  StartsWith( std::string const& substr ) {
-        return Impl::StdString::StartsWith( substr );
+    template<typename T>
+    inline Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) {
+        return Impl::MatchAllOf<T>() && m1 && m2;
     }
-    inline Impl::StdString::StartsWith  StartsWith( const char* substr ) {
-        return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) );
+    template<typename T>
+    inline Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) {
+        return Impl::MatchAllOf<T>() && m1 && m2 && m3;
     }
-    inline Impl::StdString::EndsWith    EndsWith( std::string const& substr ) {
-        return Impl::StdString::EndsWith( substr );
+    template<typename T>
+    inline Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) {
+        return Impl::MatchAnyOf<T>() || m1 || m2;
     }
-    inline Impl::StdString::EndsWith    EndsWith( const char* substr ) {
-        return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) );
+    template<typename T>
+    inline Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) {
+        return Impl::MatchAnyOf<T>() || m1 || m2 || m3;
     }
 
 } // namespace Matchers
 
 using namespace Matchers;
+using Matchers::Impl::MatcherBase;
 
 } // namespace Catch
 
@@ -1210,22 +1164,20 @@ namespace Catch {
 
     template<typename T> class ExpressionLhs;
 
-    struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison;
-
     struct CopyableStream {
         CopyableStream() {}
         CopyableStream( CopyableStream const& other ) {
             oss << other.oss.str();
         }
         CopyableStream& operator=( CopyableStream const& other ) {
-            oss.str("");
+            oss.str(std::string());
             oss << other.oss.str();
             return *this;
         }
         std::ostringstream oss;
     };
 
-    class ResultBuilder {
+    class ResultBuilder : public DecomposedExpression {
     public:
         ResultBuilder(  char const* macroName,
                         SourceLineInfo const& lineInfo,
@@ -1243,38 +1195,32 @@ namespace Catch {
             return *this;
         }
 
-        template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& );
-        template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& );
-
         ResultBuilder& setResultType( ResultWas::OfType result );
         ResultBuilder& setResultType( bool result );
-        ResultBuilder& setLhs( std::string const& lhs );
-        ResultBuilder& setRhs( std::string const& rhs );
-        ResultBuilder& setOp( std::string const& op );
 
-        void endExpression();
+        void endExpression( DecomposedExpression const& expr );
+
+        virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE;
 
-        std::string reconstructExpression() const;
         AssertionResult build() const;
+        AssertionResult build( DecomposedExpression const& expr ) const;
 
         void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal );
         void captureResult( ResultWas::OfType resultType );
         void captureExpression();
         void captureExpectedException( std::string const& expectedMessage );
-        void captureExpectedException( Matchers::Impl::Matcher<std::string> const& matcher );
+        void captureExpectedException( Matchers::Impl::MatcherBase<std::string> const& matcher );
         void handleResult( AssertionResult const& result );
         void react();
         bool shouldDebugBreak() const;
         bool allowThrows() const;
 
+        template<typename ArgT, typename MatcherT>
+        void captureMatch( ArgT const& arg, MatcherT const& matcher, char const* matcherString );
+
     private:
         AssertionInfo m_assertionInfo;
         AssertionResultData m_data;
-        struct ExprComponents {
-            ExprComponents() : testFalse( false ) {}
-            bool testFalse;
-            std::string lhs, rhs, op;
-        } m_exprComponents;
         CopyableStream m_stream;
 
         bool m_shouldDebugBreak;
@@ -1293,6 +1239,7 @@ namespace Catch {
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4389) // '==' : signed/unsigned mismatch
+#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform)
 #endif
 
 #include <cstddef>
@@ -1796,90 +1743,159 @@ std::string toString( T const& value ) {
 
 namespace Catch {
 
-// Wraps the LHS of an expression and captures the operator and RHS (if any) -
-// wrapping them all in a ResultBuilder object
-template<typename T>
-class ExpressionLhs {
-    ExpressionLhs& operator = ( ExpressionLhs const& );
-#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
-    ExpressionLhs& operator = ( ExpressionLhs && ) = delete;
-#  endif
+template<typename LhsT, Internal::Operator Op, typename RhsT>
+class BinaryExpression;
 
+template<typename ArgT, typename MatcherT>
+class MatchExpression;
+
+// Wraps the LHS of an expression and overloads comparison operators
+// for also capturing those and RHS (if any)
+template<typename T>
+class ExpressionLhs : public DecomposedExpression {
 public:
-    ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {}
-#  ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
-    ExpressionLhs( ExpressionLhs const& ) = default;
-    ExpressionLhs( ExpressionLhs && )     = default;
-#  endif
+    ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ), m_truthy(false) {}
+
+    ExpressionLhs& operator = ( const ExpressionLhs& );
 
     template<typename RhsT>
-    ResultBuilder& operator == ( RhsT const& rhs ) {
+    BinaryExpression<T, Internal::IsEqualTo, RhsT const&>
+    operator == ( RhsT const& rhs ) {
         return captureExpression<Internal::IsEqualTo>( rhs );
     }
 
     template<typename RhsT>
-    ResultBuilder& operator != ( RhsT const& rhs ) {
+    BinaryExpression<T, Internal::IsNotEqualTo, RhsT const&>
+    operator != ( RhsT const& rhs ) {
         return captureExpression<Internal::IsNotEqualTo>( rhs );
     }
 
     template<typename RhsT>
-    ResultBuilder& operator < ( RhsT const& rhs ) {
+    BinaryExpression<T, Internal::IsLessThan, RhsT const&>
+    operator < ( RhsT const& rhs ) {
         return captureExpression<Internal::IsLessThan>( rhs );
     }
 
     template<typename RhsT>
-    ResultBuilder& operator > ( RhsT const& rhs ) {
+    BinaryExpression<T, Internal::IsGreaterThan, RhsT const&>
+    operator > ( RhsT const& rhs ) {
         return captureExpression<Internal::IsGreaterThan>( rhs );
     }
 
     template<typename RhsT>
-    ResultBuilder& operator <= ( RhsT const& rhs ) {
+    BinaryExpression<T, Internal::IsLessThanOrEqualTo, RhsT const&>
+    operator <= ( RhsT const& rhs ) {
         return captureExpression<Internal::IsLessThanOrEqualTo>( rhs );
     }
 
     template<typename RhsT>
-    ResultBuilder& operator >= ( RhsT const& rhs ) {
+    BinaryExpression<T, Internal::IsGreaterThanOrEqualTo, RhsT const&>
+    operator >= ( RhsT const& rhs ) {
         return captureExpression<Internal::IsGreaterThanOrEqualTo>( rhs );
     }
 
-    ResultBuilder& operator == ( bool rhs ) {
+    BinaryExpression<T, Internal::IsEqualTo, bool> operator == ( bool rhs ) {
         return captureExpression<Internal::IsEqualTo>( rhs );
     }
 
-    ResultBuilder& operator != ( bool rhs ) {
+    BinaryExpression<T, Internal::IsNotEqualTo, bool> operator != ( bool rhs ) {
         return captureExpression<Internal::IsNotEqualTo>( rhs );
     }
 
     void endExpression() {
-        bool value = m_lhs ? true : false;
+        m_truthy = m_lhs ? true : false;
         m_rb
-            .setLhs( Catch::toString( value ) )
-            .setResultType( value )
-            .endExpression();
+            .setResultType( m_truthy )
+            .endExpression( *this );
     }
 
-    // Only simple binary expressions are allowed on the LHS.
-    // If more complex compositions are required then place the sub expression in parentheses
-    template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& );
-    template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& );
-    template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& );
-    template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& );
-    template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& );
-    template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& );
+    virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE {
+        dest = Catch::toString( m_truthy );
+    }
 
 private:
     template<Internal::Operator Op, typename RhsT>
-    ResultBuilder& captureExpression( RhsT const& rhs ) {
-        return m_rb
-            .setResultType( Internal::compare<Op>( m_lhs, rhs ) )
-            .setLhs( Catch::toString( m_lhs ) )
-            .setRhs( Catch::toString( rhs ) )
-            .setOp( Internal::OperatorTraits<Op>::getName() );
+    BinaryExpression<T, Op, RhsT&> captureExpression( RhsT& rhs ) const {
+        return BinaryExpression<T, Op, RhsT&>( m_rb, m_lhs, rhs );
+    }
+
+    template<Internal::Operator Op>
+    BinaryExpression<T, Op, bool> captureExpression( bool rhs ) const {
+        return BinaryExpression<T, Op, bool>( m_rb, m_lhs, rhs );
     }
 
 private:
     ResultBuilder& m_rb;
     T m_lhs;
+    bool m_truthy;
+};
+
+template<typename LhsT, Internal::Operator Op, typename RhsT>
+class BinaryExpression : public DecomposedExpression {
+public:
+    BinaryExpression( ResultBuilder& rb, LhsT lhs, RhsT rhs )
+        : m_rb( rb ), m_lhs( lhs ), m_rhs( rhs ) {}
+
+    BinaryExpression& operator = ( BinaryExpression& );
+
+    void endExpression() const {
+        m_rb
+            .setResultType( Internal::compare<Op>( m_lhs, m_rhs ) )
+            .endExpression( *this );
+    }
+
+    virtual bool isBinaryExpression() const CATCH_OVERRIDE {
+        return true;
+    }
+
+    virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE {
+        std::string lhs = Catch::toString( m_lhs );
+        std::string rhs = Catch::toString( m_rhs );
+        char delim = lhs.size() + rhs.size() < 40 &&
+                     lhs.find('\n') == std::string::npos &&
+                     rhs.find('\n') == std::string::npos ? ' ' : '\n';
+        dest.reserve( 7 + lhs.size() + rhs.size() );
+                   // 2 for spaces around operator
+                   // 2 for operator
+                   // 2 for parentheses (conditionally added later)
+                   // 1 for negation (conditionally added later)
+        dest = lhs;
+        dest += delim;
+        dest += Internal::OperatorTraits<Op>::getName();
+        dest += delim;
+        dest += rhs;
+    }
+
+private:
+    ResultBuilder& m_rb;
+    LhsT m_lhs;
+    RhsT m_rhs;
+};
+
+template<typename ArgT, typename MatcherT>
+class MatchExpression : public DecomposedExpression {
+public:
+    MatchExpression( ArgT arg, MatcherT matcher, char const* matcherString )
+        : m_arg( arg ), m_matcher( matcher ), m_matcherString( matcherString ) {}
+
+    virtual bool isBinaryExpression() const CATCH_OVERRIDE {
+        return true;
+    }
+
+    virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE {
+        std::string matcherAsString = m_matcher.toString();
+        dest = Catch::toString( m_arg );
+        dest += ' ';
+        if( matcherAsString == Detail::unprintableString )
+            dest += m_matcherString;
+        else
+            dest += matcherAsString;
+    }
+
+private:
+    ArgT m_arg;
+    MatcherT m_matcher;
+    char const* m_matcherString;
 };
 
 } // end namespace Catch
@@ -1896,6 +1912,14 @@ namespace Catch {
         return ExpressionLhs<bool>( *this, value );
     }
 
+    template<typename ArgT, typename MatcherT>
+    inline void ResultBuilder::captureMatch( ArgT const& arg, MatcherT const& matcher,
+                                             char const* matcherString ) {
+        MatchExpression<ArgT const&, MatcherT const&> expr( arg, matcher, matcherString );
+        setResultType( matcher.match( arg ) );
+        endExpression( expr );
+    }
+
 } // namespace Catch
 
 // #included from: catch_message.h
@@ -1998,11 +2022,19 @@ namespace Catch {
 #define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED
 
 #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
-#define CATCH_PLATFORM_MAC
+#  define CATCH_PLATFORM_MAC
 #elif  defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
-#define CATCH_PLATFORM_IPHONE
+#  define CATCH_PLATFORM_IPHONE
+#elif defined(linux) || defined(__linux) || defined(__linux__)
+#  define CATCH_PLATFORM_LINUX
 #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
-#define CATCH_PLATFORM_WINDOWS
+#  define CATCH_PLATFORM_WINDOWS
+#  if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX)
+#    define CATCH_DEFINES_NOMINMAX
+#  endif
+#  if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN)
+#    define CATCH_DEFINES_WIN32_LEAN_AND_MEAN
+#  endif
 #endif
 
 #include <string>
@@ -2017,27 +2049,36 @@ namespace Catch{
 
     // The following code snippet based on:
     // http://cocoawithlove.com/2008/03/break-into-debugger.html
-    #ifdef DEBUG
-        #if defined(__ppc64__) || defined(__ppc__)
-            #define CATCH_BREAK_INTO_DEBUGGER() \
-                if( Catch::isDebuggerActive() ) { \
-                    __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \
-                    : : : "memory","r0","r3","r4" ); \
-                }
-        #else
-            #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );}
-        #endif
+    #if defined(__ppc64__) || defined(__ppc__)
+        #define CATCH_TRAP() \
+                __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \
+                : : : "memory","r0","r3","r4" )
+    #else
+        #define CATCH_TRAP() __asm__("int $3\n" : : )
     #endif
 
+#elif defined(CATCH_PLATFORM_LINUX)
+    // If we can use inline assembler, do it because this allows us to break
+    // directly at the location of the failing check instead of breaking inside
+    // raise() called from it, i.e. one stack frame below.
+    #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))
+        #define CATCH_TRAP() asm volatile ("int $3")
+    #else // Fall back to the generic way.
+        #include <signal.h>
+
+        #define CATCH_TRAP() raise(SIGTRAP)
+    #endif
 #elif defined(_MSC_VER)
-    #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); }
+    #define CATCH_TRAP() __debugbreak()
 #elif defined(__MINGW32__)
     extern "C" __declspec(dllimport) void __stdcall DebugBreak();
-    #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); }
+    #define CATCH_TRAP() DebugBreak()
 #endif
 
-#ifndef CATCH_BREAK_INTO_DEBUGGER
-#define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue();
+#ifdef CATCH_TRAP
+    #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); }
+#else
+    #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue();
 #endif
 
 // #included from: catch_interfaces_runner.h
@@ -2052,6 +2093,53 @@ namespace Catch {
     };
 }
 
+// #included from: catch_type_traits.hpp
+#define TWOBLUECUBES_CATCH_TYPE_TRAITS_HPP_INCLUDED
+
+#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
+#include <type_traits>
+#endif
+
+namespace Catch {
+
+#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
+
+     template <typename T>
+     using add_lvalue_reference = std::add_lvalue_reference<T>;
+
+     template <typename T>
+     using add_const = std::add_const<T>;
+
+#else
+
+    template <typename T>
+    struct add_const {
+        typedef const T type;
+    };
+
+    template <typename T>
+    struct add_lvalue_reference {
+        typedef T& type;
+    };
+    template <typename T>
+    struct add_lvalue_reference<T&> {
+        typedef T& type;
+    };
+    // No && overload, because that is C++11, in which case we have
+    // proper type_traits implementation from the standard library
+
+#endif
+
+}
+
+#if defined(CATCH_CONFIG_FAST_COMPILE)
+///////////////////////////////////////////////////////////////////////////////
+// We can speedup compilation significantly by breaking into debugger lower in
+// the callstack, because then we don't have to expand CATCH_BREAK_INTO_DEBUGGER
+// macro in each assertion
+#define INTERNAL_CATCH_REACT( resultBuilder ) \
+    resultBuilder.react();
+#else
 ///////////////////////////////////////////////////////////////////////////////
 // In the event of a failure works out if the debugger needs to be invoked
 // and/or an exception thrown and takes appropriate action.
@@ -2060,6 +2148,7 @@ namespace Catch {
 #define INTERNAL_CATCH_REACT( resultBuilder ) \
     if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \
     resultBuilder.react();
+#endif
 
 ///////////////////////////////////////////////////////////////////////////////
 #define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \
@@ -2068,12 +2157,14 @@ namespace Catch {
         try { \
             CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
             ( __catchResult <= expr ).endExpression(); \
+            CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \
         } \
         catch( ... ) { \
-            __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \
+            __catchResult.useActiveException( resultDisposition ); \
         } \
         INTERNAL_CATCH_REACT( __catchResult ) \
-    } while( Catch::isTrue( false && !!(expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look
+    } while( Catch::isTrue( false && static_cast<bool>( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look
+    // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&.
 
 ///////////////////////////////////////////////////////////////////////////////
 #define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \
@@ -2090,7 +2181,7 @@ namespace Catch {
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
         try { \
-            expr; \
+            static_cast<void>(expr); \
             __catchResult.captureResult( Catch::ResultWas::Ok ); \
         } \
         catch( ... ) { \
@@ -2105,7 +2196,7 @@ namespace Catch {
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \
         if( __catchResult.allowThrows() ) \
             try { \
-                expr; \
+                static_cast<void>(expr); \
                 __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \
             } \
             catch( ... ) { \
@@ -2119,13 +2210,13 @@ namespace Catch {
 ///////////////////////////////////////////////////////////////////////////////
 #define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \
     do { \
-        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
+        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr ", " #exceptionType, resultDisposition ); \
         if( __catchResult.allowThrows() ) \
             try { \
-                expr; \
+                static_cast<void>(expr); \
                 __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \
             } \
-            catch( exceptionType ) { \
+            catch( Catch::add_const<Catch::add_lvalue_reference<exceptionType>::type>::type ) { \
                 __catchResult.captureResult( Catch::ResultWas::Ok ); \
             } \
             catch( ... ) { \
@@ -2164,13 +2255,7 @@ namespace Catch {
     do { \
         Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \
         try { \
-            std::string matcherAsString = (matcher).toString(); \
-            __catchResult \
-                .setLhs( Catch::toString( arg ) ) \
-                .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \
-                .setOp( "matches" ) \
-                .setResultType( (matcher).match( arg ) ); \
-            __catchResult.captureExpression(); \
+            __catchResult.captureMatch( arg, matcher, #matcher ); \
         } catch( ... ) { \
             __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \
         } \
@@ -2253,6 +2338,8 @@ namespace Catch {
     };
 }
 
+#include <string>
+
 namespace Catch {
 
     struct SectionInfo {
@@ -2623,6 +2710,10 @@ namespace Catch {
 #include <cmath>
 #include <limits>
 
+#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
+#include <type_traits>
+#endif
+
 namespace Catch {
 namespace Detail {
 
@@ -2630,12 +2721,14 @@ namespace Detail {
     public:
         explicit Approx ( double value )
         :   m_epsilon( std::numeric_limits<float>::epsilon()*100 ),
+            m_margin( 0.0 ),
             m_scale( 1.0 ),
             m_value( value )
         {}
 
         Approx( Approx const& other )
         :   m_epsilon( other.m_epsilon ),
+            m_margin( other.m_margin ),
             m_scale( other.m_scale ),
             m_value( other.m_value )
         {}
@@ -2647,13 +2740,69 @@ namespace Detail {
         Approx operator()( double value ) {
             Approx approx( value );
             approx.epsilon( m_epsilon );
+            approx.margin( m_margin );
             approx.scale( m_scale );
             return approx;
         }
 
+#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS)
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator == ( const T& lhs, Approx const& rhs ) {
+            // Thanks to Richard Harris for his help refining this formula
+            auto lhs_v = double(lhs);
+            bool relativeOK = std::fabs(lhs_v - rhs.m_value) < rhs.m_epsilon * (rhs.m_scale + (std::max)(std::fabs(lhs_v), std::fabs(rhs.m_value)));
+            if (relativeOK) {
+                return true;
+            }
+            return std::fabs(lhs_v - rhs.m_value) < rhs.m_margin;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator == ( Approx const& lhs, const T& rhs ) {
+            return operator==( rhs, lhs );
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator != ( T lhs, Approx const& rhs ) {
+            return !operator==( lhs, rhs );
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator != ( Approx const& lhs, T rhs ) {
+            return !operator==( rhs, lhs );
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator <= ( T lhs, Approx const& rhs )
+        {
+          return double(lhs) < rhs.m_value || lhs == rhs;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator <= ( Approx const& lhs, T rhs )
+        {
+          return lhs.m_value < double(rhs) || lhs == rhs;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator >= ( T lhs, Approx const& rhs )
+        {
+          return double(lhs) > rhs.m_value || lhs == rhs;
+        }
+
+        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
+        friend bool operator >= ( Approx const& lhs, T rhs )
+        {
+          return lhs.m_value > double(rhs) || lhs == rhs;
+        }
+#else
         friend bool operator == ( double lhs, Approx const& rhs ) {
             // Thanks to Richard Harris for his help refining this formula
-            return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) );
+            bool relativeOK = std::fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( std::fabs(lhs), std::fabs(rhs.m_value) ) );
+            if (relativeOK) {
+                return true;
+            }
+            return std::fabs(lhs - rhs.m_value) < rhs.m_margin;
         }
 
         friend bool operator == ( Approx const& lhs, double rhs ) {
@@ -2668,11 +2817,37 @@ namespace Detail {
             return !operator==( rhs, lhs );
         }
 
+        friend bool operator <= ( double lhs, Approx const& rhs )
+        {
+          return lhs < rhs.m_value || lhs == rhs;
+        }
+
+        friend bool operator <= ( Approx const& lhs, double rhs )
+        {
+          return lhs.m_value < rhs || lhs == rhs;
+        }
+
+        friend bool operator >= ( double lhs, Approx const& rhs )
+        {
+          return lhs > rhs.m_value || lhs == rhs;
+        }
+
+        friend bool operator >= ( Approx const& lhs, double rhs )
+        {
+          return lhs.m_value > rhs || lhs == rhs;
+        }
+#endif
+
         Approx& epsilon( double newEpsilon ) {
             m_epsilon = newEpsilon;
             return *this;
         }
 
+        Approx& margin( double newMargin ) {
+            m_margin = newMargin;
+            return *this;
+        }
+
         Approx& scale( double newScale ) {
             m_scale = newScale;
             return *this;
@@ -2686,6 +2861,7 @@ namespace Detail {
 
     private:
         double m_epsilon;
+        double m_margin;
         double m_scale;
         double m_value;
     };
@@ -2698,6 +2874,153 @@ inline std::string toString<Detail::Approx>( Detail::Approx const& value ) {
 
 } // end namespace Catch
 
+// #included from: internal/catch_matchers_string.h
+#define TWOBLUECUBES_CATCH_MATCHERS_STRING_H_INCLUDED
+
+namespace Catch {
+namespace Matchers {
+
+    namespace StdString {
+
+        struct CasedString
+        {
+            CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity );
+            std::string adjustString( std::string const& str ) const;
+            std::string caseSensitivitySuffix() const;
+
+            CaseSensitive::Choice m_caseSensitivity;
+            std::string m_str;
+        };
+
+        struct StringMatcherBase : MatcherBase<std::string> {
+            StringMatcherBase( std::string operation, CasedString const& comparator );
+            virtual std::string describe() const CATCH_OVERRIDE;
+
+            CasedString m_comparator;
+            std::string m_operation;
+        };
+
+        struct EqualsMatcher : StringMatcherBase {
+            EqualsMatcher( CasedString const& comparator );
+            virtual bool match( std::string const& source ) const CATCH_OVERRIDE;
+        };
+        struct ContainsMatcher : StringMatcherBase {
+            ContainsMatcher( CasedString const& comparator );
+            virtual bool match( std::string const& source ) const CATCH_OVERRIDE;
+        };
+        struct StartsWithMatcher : StringMatcherBase {
+            StartsWithMatcher( CasedString const& comparator );
+            virtual bool match( std::string const& source ) const CATCH_OVERRIDE;
+        };
+        struct EndsWithMatcher : StringMatcherBase {
+            EndsWithMatcher( CasedString const& comparator );
+            virtual bool match( std::string const& source ) const CATCH_OVERRIDE;
+        };
+
+    } // namespace StdString
+
+    // The following functions create the actual matcher objects.
+    // This allows the types to be inferred
+
+    StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+    StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+    StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+    StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
+
+} // namespace Matchers
+} // namespace Catch
+
+// #included from: internal/catch_matchers_vector.h
+#define TWOBLUECUBES_CATCH_MATCHERS_VECTOR_H_INCLUDED
+
+namespace Catch {
+namespace Matchers {
+
+    namespace Vector {
+
+        template<typename T>
+        struct ContainsElementMatcher : MatcherBase<std::vector<T>, T> {
+
+            ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {}
+
+            bool match(std::vector<T> const &v) const CATCH_OVERRIDE {
+                return std::find(v.begin(), v.end(), m_comparator) != v.end();
+            }
+
+            virtual std::string describe() const CATCH_OVERRIDE {
+                return "Contains: " + Catch::toString( m_comparator );
+            }
+
+            T const& m_comparator;
+        };
+
+        template<typename T>
+        struct ContainsMatcher : MatcherBase<std::vector<T>, std::vector<T> > {
+
+            ContainsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {}
+
+            bool match(std::vector<T> const &v) const CATCH_OVERRIDE {
+                // !TBD: see note in EqualsMatcher
+                if (m_comparator.size() > v.size())
+                    return false;
+                for (size_t i = 0; i < m_comparator.size(); ++i)
+                    if (std::find(v.begin(), v.end(), m_comparator[i]) == v.end())
+                        return false;
+                return true;
+            }
+            virtual std::string describe() const CATCH_OVERRIDE {
+                return "Contains: " + Catch::toString( m_comparator );
+            }
+
+            std::vector<T> const& m_comparator;
+        };
+
+        template<typename T>
+        struct EqualsMatcher : MatcherBase<std::vector<T>, std::vector<T> > {
+
+            EqualsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {}
+
+            bool match(std::vector<T> const &v) const CATCH_OVERRIDE {
+                // !TBD: This currently works if all elements can be compared using !=
+                // - a more general approach would be via a compare template that defaults
+                // to using !=. but could be specialised for, e.g. std::vector<T> etc
+                // - then just call that directly
+                if (m_comparator.size() != v.size())
+                    return false;
+                for (size_t i = 0; i < v.size(); ++i)
+                    if (m_comparator[i] != v[i])
+                        return false;
+                return true;
+            }
+            virtual std::string describe() const CATCH_OVERRIDE {
+                return "Equals: " + Catch::toString( m_comparator );
+            }
+            std::vector<T> const& m_comparator;
+        };
+
+    } // namespace Vector
+
+    // The following functions create the actual matcher objects.
+    // This allows the types to be inferred
+
+    template<typename T>
+    Vector::ContainsMatcher<T> Contains( std::vector<T> const& comparator ) {
+        return Vector::ContainsMatcher<T>( comparator );
+    }
+
+    template<typename T>
+    Vector::ContainsElementMatcher<T> VectorContains( T const& comparator ) {
+        return Vector::ContainsElementMatcher<T>( comparator );
+    }
+
+    template<typename T>
+    Vector::EqualsMatcher<T> Equals( std::vector<T> const& comparator ) {
+        return Vector::EqualsMatcher<T>( comparator );
+    }
+
+} // namespace Matchers
+} // namespace Catch
+
 // #included from: internal/catch_interfaces_tag_alias_registry.h
 #define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED
 
@@ -2822,7 +3145,8 @@ namespace Catch {
             IsHidden = 1 << 1,
             ShouldFail = 1 << 2,
             MayFail = 1 << 3,
-            Throws = 1 << 4
+            Throws = 1 << 4,
+            NonPortable = 1 << 5
         };
 
         TestCaseInfo(   std::string const& _name,
@@ -3078,6 +3402,29 @@ return @ desc; \
 #endif
 
 #ifdef CATCH_IMPL
+
+// !TBD: Move the leak detector code into a separate header
+#ifdef CATCH_CONFIG_WINDOWS_CRTDBG
+#include <crtdbg.h>
+class LeakDetector {
+public:
+	LeakDetector() {
+		int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
+		flag |= _CRTDBG_LEAK_CHECK_DF;
+		flag |= _CRTDBG_ALLOC_MEM_DF;
+		_CrtSetDbgFlag(flag);
+		_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+		_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
+		// Change this to leaking allocation's number to break there
+		_CrtSetBreakAlloc(-1);
+	}
+};
+#else
+class LeakDetector {};
+#endif
+
+LeakDetector leakDetector;
+
 // #included from: internal/catch_impl.hpp
 #define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED
 
@@ -3117,6 +3464,8 @@ return @ desc; \
 // #included from: catch_wildcard_pattern.hpp
 #define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED
 
+#include <stdexcept>
+
 namespace Catch
 {
     class WildcardPattern {
@@ -3134,11 +3483,11 @@ namespace Catch
             m_wildcard( NoWildcard ),
             m_pattern( adjustCase( pattern ) )
         {
-            if( startsWith( m_pattern, "*" ) ) {
+            if( startsWith( m_pattern, '*' ) ) {
                 m_pattern = m_pattern.substr( 1 );
                 m_wildcard = WildcardAtStart;
             }
-            if( endsWith( m_pattern, "*" ) ) {
+            if( endsWith( m_pattern, '*' ) ) {
                 m_pattern = m_pattern.substr( 0, m_pattern.size()-1 );
                 m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd );
             }
@@ -3257,11 +3606,12 @@ namespace Catch {
 namespace Catch {
 
     class TestSpecParser {
-        enum Mode{ None, Name, QuotedName, Tag };
+        enum Mode{ None, Name, QuotedName, Tag, EscapedName };
         Mode m_mode;
         bool m_exclusion;
         std::size_t m_start, m_pos;
         std::string m_arg;
+        std::vector<std::size_t> m_escapeChars;
         TestSpec::Filter m_currentFilter;
         TestSpec m_testSpec;
         ITagAliasRegistry const* m_tagAliases;
@@ -3274,6 +3624,7 @@ namespace Catch {
             m_exclusion = false;
             m_start = std::string::npos;
             m_arg = m_tagAliases->expandAliases( arg );
+            m_escapeChars.clear();
             for( m_pos = 0; m_pos < m_arg.size(); ++m_pos )
                 visitChar( m_arg[m_pos] );
             if( m_mode == Name )
@@ -3292,6 +3643,7 @@ namespace Catch {
                 case '~': m_exclusion = true; return;
                 case '[': return startNewMode( Tag, ++m_pos );
                 case '"': return startNewMode( QuotedName, ++m_pos );
+                case '\\': return escape();
                 default: startNewMode( Name, m_pos ); break;
                 }
             }
@@ -3307,7 +3659,11 @@ namespace Catch {
                         addPattern<TestSpec::NamePattern>();
                     startNewMode( Tag, ++m_pos );
                 }
+                else if( c == '\\' )
+                    escape();
             }
+            else if( m_mode == EscapedName )
+                m_mode = Name;
             else if( m_mode == QuotedName && c == '"' )
                 addPattern<TestSpec::NamePattern>();
             else if( m_mode == Tag && c == ']' )
@@ -3317,10 +3673,19 @@ namespace Catch {
             m_mode = mode;
             m_start = start;
         }
+        void escape() {
+            if( m_mode == None )
+                m_start = m_pos;
+            m_mode = EscapedName;
+            m_escapeChars.push_back( m_pos );
+        }
         std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); }
         template<typename T>
         void addPattern() {
             std::string token = subString();
+            for( size_t i = 0; i < m_escapeChars.size(); ++i )
+                token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 );
+            m_escapeChars.clear();
             if( startsWith( token, "exclude:" ) ) {
                 m_exclusion = true;
                 token = token.substr( 8 );
@@ -3354,7 +3719,7 @@ namespace Catch {
 // #included from: catch_interfaces_config.h
 #define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED
 
-#include <iostream>
+#include <iosfwd>
 #include <string>
 #include <vector>
 
@@ -3406,6 +3771,8 @@ namespace Catch {
         virtual RunTests::InWhatOrder runOrder() const = 0;
         virtual unsigned int rngSeed() const = 0;
         virtual UseColour::YesOrNo useColour() const = 0;
+        virtual std::vector<std::string> const& getSectionsToRun() const = 0;
+
     };
 }
 
@@ -3474,8 +3841,7 @@ namespace Catch {
 #include <memory>
 #include <vector>
 #include <string>
-#include <iostream>
-#include <ctime>
+#include <stdexcept>
 
 #ifndef CATCH_CONFIG_CONSOLE_WIDTH
 #define CATCH_CONFIG_CONSOLE_WIDTH 80
@@ -3532,6 +3898,7 @@ namespace Catch {
 
         std::vector<std::string> reporterNames;
         std::vector<std::string> testsOrTags;
+        std::vector<std::string> sectionsToRun;
     };
 
     class Config : public SharedImpl<IConfig> {
@@ -3556,8 +3923,7 @@ namespace Catch {
             }
         }
 
-        virtual ~Config() {
-        }
+        virtual ~Config() {}
 
         std::string const& getFilename() const {
             return m_data.outputFilename ;
@@ -3570,27 +3936,26 @@ namespace Catch {
 
         std::string getProcessName() const { return m_data.processName; }
 
-        bool shouldDebugBreak() const { return m_data.shouldDebugBreak; }
+        std::vector<std::string> const& getReporterNames() const { return m_data.reporterNames; }
+        std::vector<std::string> const& getSectionsToRun() const CATCH_OVERRIDE { return m_data.sectionsToRun; }
 
-        std::vector<std::string> getReporterNames() const { return m_data.reporterNames; }
-
-        int abortAfter() const { return m_data.abortAfter; }
-
-        TestSpec const& testSpec() const { return m_testSpec; }
+        virtual TestSpec const& testSpec() const CATCH_OVERRIDE { return m_testSpec; }
 
         bool showHelp() const { return m_data.showHelp; }
-        bool showInvisibles() const { return m_data.showInvisibles; }
 
         // IConfig interface
-        virtual bool allowThrows() const        { return !m_data.noThrow; }
-        virtual std::ostream& stream() const    { return m_stream->stream(); }
-        virtual std::string name() const        { return m_data.name.empty() ? m_data.processName : m_data.name; }
-        virtual bool includeSuccessfulResults() const   { return m_data.showSuccessfulTests; }
-        virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; }
-        virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; }
-        virtual RunTests::InWhatOrder runOrder() const  { return m_data.runOrder; }
-        virtual unsigned int rngSeed() const    { return m_data.rngSeed; }
-        virtual UseColour::YesOrNo useColour() const { return m_data.useColour; }
+        virtual bool allowThrows() const CATCH_OVERRIDE                 { return !m_data.noThrow; }
+        virtual std::ostream& stream() const CATCH_OVERRIDE             { return m_stream->stream(); }
+        virtual std::string name() const CATCH_OVERRIDE                 { return m_data.name.empty() ? m_data.processName : m_data.name; }
+        virtual bool includeSuccessfulResults() const CATCH_OVERRIDE    { return m_data.showSuccessfulTests; }
+        virtual bool warnAboutMissingAssertions() const CATCH_OVERRIDE  { return m_data.warnings & WarnAbout::NoAssertions; }
+        virtual ShowDurations::OrNot showDurations() const CATCH_OVERRIDE { return m_data.showDurations; }
+        virtual RunTests::InWhatOrder runOrder() const CATCH_OVERRIDE   { return m_data.runOrder; }
+        virtual unsigned int rngSeed() const CATCH_OVERRIDE             { return m_data.rngSeed; }
+        virtual UseColour::YesOrNo useColour() const CATCH_OVERRIDE     { return m_data.useColour; }
+        virtual bool shouldDebugBreak() const CATCH_OVERRIDE { return m_data.shouldDebugBreak; }
+        virtual int abortAfter() const CATCH_OVERRIDE { return m_data.abortAfter; }
+        virtual bool showInvisibles() const CATCH_OVERRIDE { return m_data.showInvisibles; }
 
     private:
 
@@ -4652,6 +5017,7 @@ STITCH_CLARA_CLOSE_NAMESPACE
 #endif
 
 #include <fstream>
+#include <ctime>
 
 namespace Catch {
 
@@ -4662,13 +5028,14 @@ namespace Catch {
         config.abortAfter = x;
     }
     inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); }
+    inline void addSectionToRun( ConfigData& config, std::string const& sectionName ) { config.sectionsToRun.push_back( sectionName ); }
     inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); }
 
     inline void addWarning( ConfigData& config, std::string const& _warning ) {
         if( _warning == "NoAssertions" )
             config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions );
         else
-            throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" );
+            throw std::runtime_error( "Unrecognised warning: '" + _warning + '\'' );
     }
     inline void setOrder( ConfigData& config, std::string const& order ) {
         if( startsWith( "declared", order ) )
@@ -4678,7 +5045,7 @@ namespace Catch {
         else if( startsWith( "random", order ) )
             config.runOrder = RunTests::InRandomOrder;
         else
-            throw std::runtime_error( "Unrecognised ordering: '" + order + "'" );
+            throw std::runtime_error( "Unrecognised ordering: '" + order + '\'' );
     }
     inline void setRngSeed( ConfigData& config, std::string const& seed ) {
         if( seed == "time" ) {
@@ -4689,7 +5056,7 @@ namespace Catch {
             ss << seed;
             ss >> config.rngSeed;
             if( ss.fail() )
-                throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" );
+                throw std::runtime_error( "Argument to --rng-seed should be the word 'time' or a number" );
         }
     }
     inline void setVerbosity( ConfigData& config, int level ) {
@@ -4724,10 +5091,10 @@ namespace Catch {
         std::string line;
         while( std::getline( f, line ) ) {
             line = trim(line);
-            if( !line.empty() && !startsWith( line, "#" ) ) {
-                if( !startsWith( line, "\"" ) )
-                    line = "\"" + line + "\"";
-                addTestOrTags( config, line + "," );
+            if( !line.empty() && !startsWith( line, '#' ) ) {
+                if( !startsWith( line, '"' ) )
+                    line = '"' + line + '"';
+                addTestOrTags( config, line + ',' );
             }
         }
     }
@@ -4815,6 +5182,10 @@ namespace Catch {
             .describe( "adds a tag for the filename" )
             .bind( &ConfigData::filenamesAsTags );
 
+        cli["-c"]["--section"]
+                .describe( "specify section to run" )
+                .bind( &addSectionToRun, "section name" );
+
         // Less common commands which don't have a short form
         cli["--list-test-names-only"]
             .describe( "list all/matching test cases names only" )
@@ -4887,19 +5258,16 @@ namespace Tbc {
         TextAttributes()
         :   initialIndent( std::string::npos ),
             indent( 0 ),
-            width( consoleWidth-1 ),
-            tabChar( '\t' )
+            width( consoleWidth-1 )
         {}
 
         TextAttributes& setInitialIndent( std::size_t _value )  { initialIndent = _value; return *this; }
         TextAttributes& setIndent( std::size_t _value )         { indent = _value; return *this; }
         TextAttributes& setWidth( std::size_t _value )          { width = _value; return *this; }
-        TextAttributes& setTabChar( char _value )               { tabChar = _value; return *this; }
 
         std::size_t initialIndent;  // indent of first line, or npos
         std::size_t indent;         // indent of subsequent lines, or all if initialIndent is npos
         std::size_t width;          // maximum width of text, including indent. Longer text will wrap
-        char tabChar;               // If this char is seen the indent is changed to current pos
     };
 
     class Text {
@@ -4907,62 +5275,76 @@ namespace Tbc {
         Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() )
         : attr( _attr )
         {
-            std::string wrappableChars = " [({.,/|\\-";
-            std::size_t indent = _attr.initialIndent != std::string::npos
-                ? _attr.initialIndent
-                : _attr.indent;
-            std::string remainder = _str;
+            const std::string wrappableBeforeChars = "[({<\t";
+            const std::string wrappableAfterChars = "])}>-,./|\\";
+            const std::string wrappableInsteadOfChars = " \n\r";
+            std::string indent = _attr.initialIndent != std::string::npos
+                ? std::string( _attr.initialIndent, ' ' )
+                : std::string( _attr.indent, ' ' );
+
+            typedef std::string::const_iterator iterator;
+            iterator it = _str.begin();
+            const iterator strEnd = _str.end();
+
+            while( it != strEnd ) {
 
-            while( !remainder.empty() ) {
                 if( lines.size() >= 1000 ) {
                     lines.push_back( "... message truncated due to excessive size" );
                     return;
                 }
-                std::size_t tabPos = std::string::npos;
-                std::size_t width = (std::min)( remainder.size(), _attr.width - indent );
-                std::size_t pos = remainder.find_first_of( '\n' );
-                if( pos <= width ) {
-                    width = pos;
-                }
-                pos = remainder.find_last_of( _attr.tabChar, width );
-                if( pos != std::string::npos ) {
-                    tabPos = pos;
-                    if( remainder[width] == '\n' )
-                        width--;
-                    remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 );
-                }
 
-                if( width == remainder.size() ) {
-                    spliceLine( indent, remainder, width );
-                }
-                else if( remainder[width] == '\n' ) {
-                    spliceLine( indent, remainder, width );
-                    if( width <= 1 || remainder.size() != 1 )
-                        remainder = remainder.substr( 1 );
-                    indent = _attr.indent;
-                }
-                else {
-                    pos = remainder.find_last_of( wrappableChars, width );
-                    if( pos != std::string::npos && pos > 0 ) {
-                        spliceLine( indent, remainder, pos );
-                        if( remainder[0] == ' ' )
-                            remainder = remainder.substr( 1 );
+                std::string suffix;
+                std::size_t width = (std::min)( static_cast<size_t>( strEnd-it ), _attr.width-static_cast<size_t>( indent.size() ) );
+                iterator itEnd = it+width;
+                iterator itNext = _str.end();
+
+                iterator itNewLine = std::find( it, itEnd, '\n' );
+                if( itNewLine != itEnd )
+                    itEnd = itNewLine;
+
+                if( itEnd != strEnd  ) {
+                    bool foundWrapPoint = false;
+                    iterator findIt = itEnd;
+                    do {
+                        if( wrappableAfterChars.find( *findIt ) != std::string::npos && findIt != itEnd ) {
+                            itEnd = findIt+1;
+                            itNext = findIt+1;
+                            foundWrapPoint = true;
+                        }
+                        else if( findIt > it && wrappableBeforeChars.find( *findIt ) != std::string::npos ) {
+                            itEnd = findIt;
+                            itNext = findIt;
+                            foundWrapPoint = true;
+                        }
+                        else if( wrappableInsteadOfChars.find( *findIt ) != std::string::npos ) {
+                            itNext = findIt+1;
+                            itEnd = findIt;
+                            foundWrapPoint = true;
+                        }
+                        if( findIt == it )
+                            break;
+                        else
+                            --findIt;
                     }
-                    else {
-                        spliceLine( indent, remainder, width-1 );
-                        lines.back() += "-";
+                    while( !foundWrapPoint );
+
+                    if( !foundWrapPoint ) {
+                        // No good wrap char, so we'll break mid word and add a hyphen
+                        --itEnd;
+                        itNext = itEnd;
+                        suffix = "-";
+                    }
+                    else {
+                        while( itEnd > it && wrappableInsteadOfChars.find( *(itEnd-1) ) != std::string::npos )
+                            --itEnd;
                     }
-                    if( lines.size() == 1 )
-                        indent = _attr.indent;
-                    if( tabPos != std::string::npos )
-                        indent += tabPos;
                 }
-            }
-        }
+                lines.push_back( indent + std::string( it, itEnd ) + suffix );
 
-        void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) {
-            lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) );
-            _remainder = _remainder.substr( _pos );
+                if( indent.size() != _attr.indent )
+                    indent = std::string( _attr.indent, ' ' );
+                it = itNext;
+            }
         }
 
         typedef std::vector<std::string>::const_iterator const_iterator;
@@ -5071,7 +5453,6 @@ namespace Catch {
 #include <string>
 #include <ostream>
 #include <map>
-#include <assert.h>
 
 namespace Catch
 {
@@ -5359,9 +5740,9 @@ namespace Catch {
         }
 
         if( !config.testSpec().hasFilters() )
-            Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl;
+            Catch::cout() << pluralise( matchedTests, "test case" ) << '\n' << std::endl;
         else
-            Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl;
+            Catch::cout() << pluralise( matchedTests, "matching test case" ) << '\n' << std::endl;
         return matchedTests;
     }
 
@@ -5376,8 +5757,8 @@ namespace Catch {
                 ++it ) {
             matchedTests++;
             TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
-            if( startsWith( testCaseInfo.name, "#" ) )
-               Catch::cout() << "\"" << testCaseInfo.name << "\"" << std::endl;
+            if( startsWith( testCaseInfo.name, '#' ) )
+               Catch::cout() << '"' << testCaseInfo.name << '"' << std::endl;
             else
                Catch::cout() << testCaseInfo.name << std::endl;
         }
@@ -5440,9 +5821,9 @@ namespace Catch {
                                                     .setInitialIndent( 0 )
                                                     .setIndent( oss.str().size() )
                                                     .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) );
-            Catch::cout() << oss.str() << wrapper << "\n";
+            Catch::cout() << oss.str() << wrapper << '\n';
         }
-        Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl;
+        Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl;
         return tagCounts.size();
     }
 
@@ -5461,9 +5842,9 @@ namespace Catch {
                                                         .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) );
             Catch::cout() << "  "
                     << it->first
-                    << ":"
+                    << ':'
                     << std::string( maxNameLen - it->first.size() + 2, ' ' )
-                    << wrapper << "\n";
+                    << wrapper << '\n';
         }
         Catch::cout() << std::endl;
         return factories.size();
@@ -5494,15 +5875,27 @@ namespace Catch {
 #include <string>
 #include <assert.h>
 #include <vector>
+#include <iterator>
+#include <stdexcept>
 
 namespace Catch {
 namespace TestCaseTracking {
 
+    struct NameAndLocation {
+        std::string name;
+        SourceLineInfo location;
+
+        NameAndLocation( std::string const& _name, SourceLineInfo const& _location )
+        :   name( _name ),
+            location( _location )
+        {}
+    };
+
     struct ITracker : SharedImpl<> {
         virtual ~ITracker();
 
         // static queries
-        virtual std::string name() const = 0;
+        virtual NameAndLocation const& nameAndLocation() const = 0;
 
         // dynamic queries
         virtual bool isComplete() const = 0; // Successfully completed or failed
@@ -5518,7 +5911,7 @@ namespace TestCaseTracking {
         virtual void markAsNeedingAnotherRun() = 0;
 
         virtual void addChild( Ptr<ITracker> const& child ) = 0;
-        virtual ITracker* findChild( std::string const& name ) = 0;
+        virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) = 0;
         virtual void openChild() = 0;
 
         // Debug/ checking
@@ -5526,7 +5919,7 @@ namespace TestCaseTracking {
         virtual bool isIndexTracker() const = 0;
     };
 
-    class TrackerContext {
+    class  TrackerContext {
 
         enum RunState {
             NotStarted,
@@ -5588,30 +5981,32 @@ namespace TestCaseTracking {
             Failed
         };
         class TrackerHasName {
-            std::string m_name;
+            NameAndLocation m_nameAndLocation;
         public:
-            TrackerHasName( std::string const& name ) : m_name( name ) {}
+            TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {}
             bool operator ()( Ptr<ITracker> const& tracker ) {
-                return tracker->name() == m_name;
+                return
+                    tracker->nameAndLocation().name == m_nameAndLocation.name &&
+                    tracker->nameAndLocation().location == m_nameAndLocation.location;
             }
         };
         typedef std::vector<Ptr<ITracker> > Children;
-        std::string m_name;
+        NameAndLocation m_nameAndLocation;
         TrackerContext& m_ctx;
         ITracker* m_parent;
         Children m_children;
         CycleState m_runState;
     public:
-        TrackerBase( std::string const& name, TrackerContext& ctx, ITracker* parent )
-        :   m_name( name ),
+        TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
+        :   m_nameAndLocation( nameAndLocation ),
             m_ctx( ctx ),
             m_parent( parent ),
             m_runState( NotStarted )
         {}
         virtual ~TrackerBase();
 
-        virtual std::string name() const CATCH_OVERRIDE {
-            return m_name;
+        virtual NameAndLocation const& nameAndLocation() const CATCH_OVERRIDE {
+            return m_nameAndLocation;
         }
         virtual bool isComplete() const CATCH_OVERRIDE {
             return m_runState == CompletedSuccessfully || m_runState == Failed;
@@ -5630,8 +6025,8 @@ namespace TestCaseTracking {
             m_children.push_back( child );
         }
 
-        virtual ITracker* findChild( std::string const& name ) CATCH_OVERRIDE {
-            Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( name ) );
+        virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) CATCH_OVERRIDE {
+            Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) );
             return( it != m_children.end() )
                 ? it->get()
                 : CATCH_NULL;
@@ -5709,32 +6104,56 @@ namespace TestCaseTracking {
     };
 
     class SectionTracker : public TrackerBase {
+        std::vector<std::string> m_filters;
     public:
-        SectionTracker( std::string const& name, TrackerContext& ctx, ITracker* parent )
-        :   TrackerBase( name, ctx, parent )
-        {}
+        SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )
+        :   TrackerBase( nameAndLocation, ctx, parent )
+        {
+            if( parent ) {
+                while( !parent->isSectionTracker() )
+                    parent = &parent->parent();
+
+                SectionTracker& parentSection = static_cast<SectionTracker&>( *parent );
+                addNextFilters( parentSection.m_filters );
+            }
+        }
         virtual ~SectionTracker();
 
         virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; }
 
-        static SectionTracker& acquire( TrackerContext& ctx, std::string const& name ) {
+        static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) {
             SectionTracker* section = CATCH_NULL;
 
             ITracker& currentTracker = ctx.currentTracker();
-            if( ITracker* childTracker = currentTracker.findChild( name ) ) {
+            if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) {
                 assert( childTracker );
                 assert( childTracker->isSectionTracker() );
                 section = static_cast<SectionTracker*>( childTracker );
             }
             else {
-                section = new SectionTracker( name, ctx, &currentTracker );
+                section = new SectionTracker( nameAndLocation, ctx, &currentTracker );
                 currentTracker.addChild( section );
             }
-            if( !ctx.completedCycle() && !section->isComplete() ) {
+            if( !ctx.completedCycle() )
+                section->tryOpen();
+            return *section;
+        }
+
+        void tryOpen() {
+            if( !isComplete() && (m_filters.empty() || m_filters[0].empty() ||  m_filters[0] == m_nameAndLocation.name ) )
+                open();
+        }
 
-                section->open();
+        void addInitialFilters( std::vector<std::string> const& filters ) {
+            if( !filters.empty() ) {
+                m_filters.push_back(""); // Root - should never be consulted
+                m_filters.push_back(""); // Test Case - not a section filter
+                std::copy( filters.begin(), filters.end(), std::back_inserter( m_filters ) );
             }
-            return *section;
+        }
+        void addNextFilters( std::vector<std::string> const& filters ) {
+            if( filters.size() > 1 )
+                std::copy( filters.begin()+1, filters.end(), std::back_inserter( m_filters ) );
         }
     };
 
@@ -5742,8 +6161,8 @@ namespace TestCaseTracking {
         int m_size;
         int m_index;
     public:
-        IndexTracker( std::string const& name, TrackerContext& ctx, ITracker* parent, int size )
-        :   TrackerBase( name, ctx, parent ),
+        IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size )
+        :   TrackerBase( nameAndLocation, ctx, parent ),
             m_size( size ),
             m_index( -1 )
         {}
@@ -5751,17 +6170,17 @@ namespace TestCaseTracking {
 
         virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; }
 
-        static IndexTracker& acquire( TrackerContext& ctx, std::string const& name, int size ) {
+        static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) {
             IndexTracker* tracker = CATCH_NULL;
 
             ITracker& currentTracker = ctx.currentTracker();
-            if( ITracker* childTracker = currentTracker.findChild( name ) ) {
+            if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) {
                 assert( childTracker );
                 assert( childTracker->isIndexTracker() );
                 tracker = static_cast<IndexTracker*>( childTracker );
             }
             else {
-                tracker = new IndexTracker( name, ctx, &currentTracker, size );
+                tracker = new IndexTracker( nameAndLocation, ctx, &currentTracker, size );
                 currentTracker.addChild( tracker );
             }
 
@@ -5789,7 +6208,7 @@ namespace TestCaseTracking {
     };
 
     inline ITracker& TrackerContext::startRun() {
-        m_rootTracker = new SectionTracker( "{root}", *this, CATCH_NULL );
+        m_rootTracker = new SectionTracker( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, CATCH_NULL );
         m_currentTracker = CATCH_NULL;
         m_runState = Executing;
         return *m_rootTracker;
@@ -5809,35 +6228,138 @@ using TestCaseTracking::IndexTracker;
 
 namespace Catch {
 
-    // Report the error condition then exit the process
-    inline void fatal( std::string const& message, int exitCode ) {
+    // Report the error condition
+    inline void reportFatal( std::string const& message ) {
         IContext& context = Catch::getCurrentContext();
         IResultCapture* resultCapture = context.getResultCapture();
         resultCapture->handleFatalErrorCondition( message );
-
-		if( Catch::alwaysTrue() ) // avoids "no return" warnings
-            exit( exitCode );
     }
 
 } // namespace Catch
 
 #if defined ( CATCH_PLATFORM_WINDOWS ) /////////////////////////////////////////
+// #included from: catch_windows_h_proxy.h
+
+#define TWOBLUECUBES_CATCH_WINDOWS_H_PROXY_H_INCLUDED
+
+#ifdef CATCH_DEFINES_NOMINMAX
+#  define NOMINMAX
+#endif
+#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN
+#  define WIN32_LEAN_AND_MEAN
+#endif
+
+#ifdef __AFXDLL
+#include <AfxWin.h>
+#else
+#include <windows.h>
+#endif
+
+#ifdef CATCH_DEFINES_NOMINMAX
+#  undef NOMINMAX
+#endif
+#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN
+#  undef WIN32_LEAN_AND_MEAN
+#endif
+
+
+#  if !defined ( CATCH_CONFIG_WINDOWS_SEH )
+
+namespace Catch {
+    struct FatalConditionHandler {
+        void reset() {}
+    };
+}
+
+#  else // CATCH_CONFIG_WINDOWS_SEH is defined
 
 namespace Catch {
 
+    struct SignalDefs { DWORD id; const char* name; };
+    extern SignalDefs signalDefs[];
+    // There is no 1-1 mapping between signals and windows exceptions.
+    // Windows can easily distinguish between SO and SigSegV,
+    // but SigInt, SigTerm, etc are handled differently.
+    SignalDefs signalDefs[] = {
+        { EXCEPTION_ILLEGAL_INSTRUCTION,  "SIGILL - Illegal instruction signal" },
+        { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" },
+        { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" },
+        { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" },
+    };
+
     struct FatalConditionHandler {
-		void reset() {}
-	};
+
+        static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) {
+            for (int i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {
+                if (ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) {
+                    reset();
+                    reportFatal(signalDefs[i].name);
+                }
+            }
+            // If its not an exception we care about, pass it along.
+            // This stops us from eating debugger breaks etc.
+            return EXCEPTION_CONTINUE_SEARCH;
+        }
+
+        FatalConditionHandler() {
+            isSet = true;
+            // 32k seems enough for Catch to handle stack overflow,
+            // but the value was found experimentally, so there is no strong guarantee
+            guaranteeSize = 32 * 1024;
+            exceptionHandlerHandle = CATCH_NULL;
+            // Register as first handler in current chain
+            exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);
+            // Pass in guarantee size to be filled
+            SetThreadStackGuarantee(&guaranteeSize);
+        }
+
+        static void reset() {
+            if (isSet) {
+                // Unregister handler and restore the old guarantee
+                RemoveVectoredExceptionHandler(exceptionHandlerHandle);
+                SetThreadStackGuarantee(&guaranteeSize);
+                exceptionHandlerHandle = CATCH_NULL;
+                isSet = false;
+            }
+        }
+
+        ~FatalConditionHandler() {
+            reset();
+        }
+    private:
+        static bool isSet;
+        static ULONG guaranteeSize;
+        static PVOID exceptionHandlerHandle;
+    };
+
+    bool FatalConditionHandler::isSet = false;
+    ULONG FatalConditionHandler::guaranteeSize = 0;
+    PVOID FatalConditionHandler::exceptionHandlerHandle = CATCH_NULL;
 
 } // namespace Catch
 
+#  endif // CATCH_CONFIG_WINDOWS_SEH
+
 #else // Not Windows - assumed to be POSIX compatible //////////////////////////
 
+#  if !defined(CATCH_CONFIG_POSIX_SIGNALS)
+
+namespace Catch {
+    struct FatalConditionHandler {
+        void reset() {}
+    };
+}
+
+#  else // CATCH_CONFIG_POSIX_SIGNALS is defined
+
 #include <signal.h>
 
 namespace Catch {
 
-    struct SignalDefs { int id; const char* name; };
+    struct SignalDefs {
+        int id;
+        const char* name;
+    };
     extern SignalDefs signalDefs[];
     SignalDefs signalDefs[] = {
             { SIGINT,  "SIGINT - Terminal interrupt signal" },
@@ -5846,37 +6368,70 @@ namespace Catch {
             { SIGSEGV, "SIGSEGV - Segmentation violation signal" },
             { SIGTERM, "SIGTERM - Termination request signal" },
             { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
-        };
+    };
 
     struct FatalConditionHandler {
 
+        static bool isSet;
+        static struct sigaction oldSigActions [sizeof(signalDefs)/sizeof(SignalDefs)];
+        static stack_t oldSigStack;
+        static char altStackMem[SIGSTKSZ];
+
         static void handleSignal( int sig ) {
-            for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
-                if( sig == signalDefs[i].id )
-                    fatal( signalDefs[i].name, -sig );
-            fatal( "<unknown signal>", -sig );
+            std::string name = "<unknown signal>";
+            for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {
+                SignalDefs &def = signalDefs[i];
+                if (sig == def.id) {
+                    name = def.name;
+                    break;
+                }
+            }
+            reset();
+            reportFatal(name);
+            raise( sig );
         }
 
-        FatalConditionHandler() : m_isSet( true ) {
-            for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
-                signal( signalDefs[i].id, handleSignal );
+        FatalConditionHandler() {
+            isSet = true;
+            stack_t sigStack;
+            sigStack.ss_sp = altStackMem;
+            sigStack.ss_size = SIGSTKSZ;
+            sigStack.ss_flags = 0;
+            sigaltstack(&sigStack, &oldSigStack);
+            struct sigaction sa = { 0 };
+
+            sa.sa_handler = handleSignal;
+            sa.sa_flags = SA_ONSTACK;
+            for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) {
+                sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);
+            }
         }
+
         ~FatalConditionHandler() {
             reset();
         }
-        void reset() {
-            if( m_isSet ) {
-                for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
-                    signal( signalDefs[i].id, SIG_DFL );
-                m_isSet = false;
+        static void reset() {
+            if( isSet ) {
+                // Set signals back to previous values -- hopefully nobody overwrote them in the meantime
+                for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) {
+                    sigaction(signalDefs[i].id, &oldSigActions[i], CATCH_NULL);
+                }
+                // Return the old stack
+                sigaltstack(&oldSigStack, CATCH_NULL);
+                isSet = false;
             }
         }
-
-        bool m_isSet;
     };
 
+    bool FatalConditionHandler::isSet = false;
+    struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {};
+    stack_t FatalConditionHandler::oldSigStack = {};
+    char FatalConditionHandler::altStackMem[SIGSTKSZ] = {};
+
 } // namespace Catch
 
+#  endif // CATCH_CONFIG_POSIX_SIGNALS
+
 #endif // not Windows
 
 #include <set>
@@ -5953,10 +6508,12 @@ namespace Catch {
             m_activeTestCase = &testCase;
 
             do {
-                m_trackerContext.startRun();
+                ITracker& rootTracker = m_trackerContext.startRun();
+                assert( rootTracker.isSectionTracker() );
+                static_cast<SectionTracker&>( rootTracker ).addInitialFilters( m_config->getSectionsToRun() );
                 do {
                     m_trackerContext.startCycle();
-                    m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, testInfo.name );
+                    m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( testInfo.name, testInfo.lineInfo ) );
                     runCurrentTest( redirectedCout, redirectedCerr );
                 }
                 while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() );
@@ -6001,7 +6558,7 @@ namespace Catch {
                 m_messages.clear();
 
             // Reset working state
-            m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition );
+            m_lastAssertionInfo = AssertionInfo( std::string(), m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition );
             m_lastResult = result;
         }
 
@@ -6010,10 +6567,7 @@ namespace Catch {
             Counts& assertions
         )
         {
-            std::ostringstream oss;
-            oss << sectionInfo.name << "@" << sectionInfo.lineInfo;
-
-            ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, oss.str() );
+            ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( sectionInfo.name, sectionInfo.lineInfo ) );
             if( !sectionTracker.isOpen() )
                 return false;
             m_activeSections.push_back( &sectionTracker );
@@ -6072,7 +6626,7 @@ namespace Catch {
         virtual std::string getCurrentTestName() const {
             return m_activeTestCase
                 ? m_activeTestCase->getTestCaseInfo().name
-                : "";
+                : std::string();
         }
 
         virtual const AssertionResult* getLastResult() const {
@@ -6102,11 +6656,11 @@ namespace Catch {
             deltaTotals.testCases.failed = 1;
             m_reporter->testCaseEnded( TestCaseStats(   testInfo,
                                                         deltaTotals,
-                                                        "",
-                                                        "",
+                                                        std::string(),
+                                                        std::string(),
                                                         false ) );
             m_totals.testCases.failed++;
-            testGroupEnded( "", m_totals, 1, 1 );
+            testGroupEnded( std::string(), m_totals, 1, 1 );
             m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) );
         }
 
@@ -6125,7 +6679,7 @@ namespace Catch {
             Counts prevAssertions = m_totals.assertions;
             double duration = 0;
             try {
-                m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal );
+                m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, std::string(), ResultDisposition::Normal );
 
                 seedRng( *m_config );
 
@@ -6455,13 +7009,8 @@ namespace Catch {
 #include <vector>
 #include <set>
 #include <sstream>
-#include <iostream>
 #include <algorithm>
 
-#ifdef CATCH_CPP14_OR_GREATER
-#include <random>
-#endif
-
 namespace Catch {
 
     struct RandomNumberGenerator {
@@ -6469,7 +7018,7 @@ namespace Catch {
 
         result_type operator()( result_type n ) const { return std::rand() % n; }
 
-#ifdef CATCH_CPP14_OR_GREATER
+#ifdef CATCH_CONFIG_CPP11_SHUFFLE
         static constexpr result_type min() { return 0; }
         static constexpr result_type max() { return 1000000; }
         result_type operator()() const { return std::rand() % max(); }
@@ -6477,7 +7026,7 @@ namespace Catch {
         template<typename V>
         static void shuffle( V& vector ) {
             RandomNumberGenerator rng;
-#ifdef CATCH_CPP14_OR_GREATER
+#ifdef CATCH_CONFIG_CPP11_SHUFFLE
             std::shuffle( vector.begin(), vector.end(), rng );
 #else
             std::random_shuffle( vector.begin(), vector.end(), rng );
@@ -6520,7 +7069,7 @@ namespace Catch {
 
                 ss  << Colour( Colour::Red )
                     << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n"
-                    << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n"
+                    << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << '\n'
                     << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl;
 
                 throw std::runtime_error(ss.str());
@@ -6552,7 +7101,7 @@ namespace Catch {
 
         virtual void registerTest( TestCase const& testCase ) {
             std::string name = testCase.getTestCaseInfo().name;
-            if( name == "" ) {
+            if( name.empty() ) {
                 std::ostringstream oss;
                 oss << "Anonymous test case " << ++m_unnamedCount;
                 return registerTest( testCase.withName( oss.str() ) );
@@ -6601,7 +7150,7 @@ namespace Catch {
 
     inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) {
         std::string className = classOrQualifiedMethodName;
-        if( startsWith( className, "&" ) )
+        if( startsWith( className, '&' ) )
         {
             std::size_t lastColons = className.rfind( "::" );
             std::size_t penultimateColons = className.rfind( "::", lastColons-1 );
@@ -6819,7 +7368,7 @@ namespace Catch {
 // #included from: catch_notimplemented_exception.hpp
 #define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED
 
-#include <ostream>
+#include <sstream>
 
 namespace Catch {
 
@@ -6891,7 +7440,7 @@ namespace Catch {
         m_ofs.open( filename.c_str() );
         if( m_ofs.fail() ) {
             std::ostringstream oss;
-            oss << "Unable to open file: '" << filename << "'";
+            oss << "Unable to open file: '" << filename << '\'';
             throw std::domain_error( oss.str() );
         }
     }
@@ -6944,6 +7493,11 @@ namespace Catch {
         Context( Context const& );
         void operator=( Context const& );
 
+    public:
+        virtual ~Context() {
+            deleteAllValues( m_generatorsByTestName );
+        }
+
     public: // IContext
         virtual IResultCapture* getResultCapture() {
             return m_resultCapture;
@@ -7057,16 +7611,6 @@ namespace Catch {
 
 #if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) /////////////////////////////////////////
 
-#ifndef NOMINMAX
-#define NOMINMAX
-#endif
-
-#ifdef __AFXDLL
-#include <AfxWin.h>
-#else
-#include <windows.h>
-#endif
-
 namespace Catch {
 namespace {
 
@@ -7147,7 +7691,7 @@ namespace {
                 case Colour::White:     return setColour( "[0m" );
                 case Colour::Red:       return setColour( "[0;31m" );
                 case Colour::Green:     return setColour( "[0;32m" );
-                case Colour::Blue:      return setColour( "[0:34m" );
+                case Colour::Blue:      return setColour( "[0;34m" );
                 case Colour::Cyan:      return setColour( "[0;36m" );
                 case Colour::Yellow:    return setColour( "[0;33m" );
                 case Colour::Grey:      return setColour( "[1;30m" );
@@ -7333,7 +7877,7 @@ namespace Catch {
 
     std::string AssertionResult::getExpression() const {
         if( isFalseTest( m_info.resultDisposition ) )
-            return "!" + m_info.capturedExpression;
+            return '!' + m_info.capturedExpression;
         else
             return m_info.capturedExpression;
     }
@@ -7349,7 +7893,7 @@ namespace Catch {
     }
 
     std::string AssertionResult::getExpandedExpression() const {
-        return m_resultData.reconstructedExpression;
+        return m_resultData.reconstructExpression();
     }
 
     std::string AssertionResult::getMessage() const {
@@ -7363,15 +7907,25 @@ namespace Catch {
         return m_info.macroName;
     }
 
+    void AssertionResult::discardDecomposedExpression() const {
+        m_resultData.decomposedExpression = CATCH_NULL;
+    }
+
+    void AssertionResult::expandDecomposedExpression() const {
+        m_resultData.reconstructExpression();
+    }
+
 } // end namespace Catch
 
 // #included from: catch_test_case_info.hpp
 #define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED
 
+#include <cctype>
+
 namespace Catch {
 
     inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) {
-        if( startsWith( tag, "." ) ||
+        if( startsWith( tag, '.' ) ||
             tag == "hide" ||
             tag == "!hide" )
             return TestCaseInfo::IsHidden;
@@ -7381,11 +7935,13 @@ namespace Catch {
             return TestCaseInfo::ShouldFail;
         else if( tag == "!mayfail" )
             return TestCaseInfo::MayFail;
+        else if( tag == "!nonportable" )
+            return TestCaseInfo::NonPortable;
         else
             return TestCaseInfo::None;
     }
     inline bool isReservedTag( std::string const& tag ) {
-        return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] );
+        return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] );
     }
     inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {
         if( isReservedTag( tag ) ) {
@@ -7455,7 +8011,7 @@ namespace Catch {
 
         std::ostringstream oss;
         for( std::set<std::string>::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) {
-            oss << "[" << *it << "]";
+            oss << '[' << *it << ']';
             std::string lcaseTag = toLower( *it );
             testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) );
             testCaseInfo.lcaseTags.insert( lcaseTag );
@@ -7571,18 +8127,18 @@ namespace Catch {
     {}
 
     std::ostream& operator << ( std::ostream& os, Version const& version ) {
-        os  << version.majorVersion << "."
-            << version.minorVersion << "."
+        os  << version.majorVersion << '.'
+            << version.minorVersion << '.'
             << version.patchNumber;
 
         if( !version.branchName.empty() ) {
-            os  << "-" << version.branchName
-                << "." << version.buildNumber;
+            os  << '-' << version.branchName
+                << '.' << version.buildNumber;
         }
         return os;
     }
 
-    Version libraryVersion( 1, 5, 9, "", 0 );
+    Version libraryVersion( 1, 8, 1, "", 0 );
 
 }
 
@@ -7753,9 +8309,11 @@ namespace Catch
 #endif
 
 #ifdef CATCH_PLATFORM_WINDOWS
-#include <windows.h>
+
 #else
+
 #include <sys/time.h>
+
 #endif
 
 namespace Catch {
@@ -7802,19 +8360,28 @@ namespace Catch {
 // #included from: catch_common.hpp
 #define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED
 
+#include <cstring>
+#include <cctype>
+
 namespace Catch {
 
     bool startsWith( std::string const& s, std::string const& prefix ) {
-        return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix;
+        return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin());
+    }
+    bool startsWith( std::string const& s, char prefix ) {
+        return !s.empty() && s[0] == prefix;
     }
     bool endsWith( std::string const& s, std::string const& suffix ) {
-        return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix;
+        return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin());
+    }
+    bool endsWith( std::string const& s, char suffix ) {
+        return !s.empty() && s[s.size()-1] == suffix;
     }
     bool contains( std::string const& s, std::string const& infix ) {
         return s.find( infix ) != std::string::npos;
     }
     char toLowerCh(char c) {
-        return static_cast<char>( ::tolower( c ) );
+        return static_cast<char>( std::tolower( c ) );
     }
     void toLowerInPlace( std::string& s ) {
         std::transform( s.begin(), s.end(), s.begin(), toLowerCh );
@@ -7829,7 +8396,7 @@ namespace Catch {
         std::string::size_type start = str.find_first_not_of( whitespaceChars );
         std::string::size_type end = str.find_last_not_of( whitespaceChars );
 
-        return start != std::string::npos ? str.substr( start, 1+end-start ) : "";
+        return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string();
     }
 
     bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) {
@@ -7852,29 +8419,25 @@ namespace Catch {
     {}
 
     std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) {
-        os << pluraliser.m_count << " " << pluraliser.m_label;
+        os << pluraliser.m_count << ' ' << pluraliser.m_label;
         if( pluraliser.m_count != 1 )
-            os << "s";
+            os << 's';
         return os;
     }
 
-    SourceLineInfo::SourceLineInfo() : line( 0 ){}
+    SourceLineInfo::SourceLineInfo() : file(""), line( 0 ){}
     SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line )
     :   file( _file ),
         line( _line )
     {}
-    SourceLineInfo::SourceLineInfo( SourceLineInfo const& other )
-    :   file( other.file ),
-        line( other.line )
-    {}
     bool SourceLineInfo::empty() const {
-        return file.empty();
+        return file[0] == '\0';
     }
     bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const {
-        return line == other.line && file == other.file;
+        return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0);
     }
     bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const {
-        return line < other.line || ( line == other.line  && file < other.file );
+        return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0));
     }
 
     void seedRng( IConfig const& config ) {
@@ -7887,16 +8450,16 @@ namespace Catch {
 
     std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) {
 #ifndef __GNUG__
-        os << info.file << "(" << info.line << ")";
+        os << info.file << '(' << info.line << ')';
 #else
-        os << info.file << ":" << info.line;
+        os << info.file << ':' << info.line;
 #endif
         return os;
     }
 
     void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) {
         std::ostringstream oss;
-        oss << locationInfo << ": Internal Catch error: '" << message << "'";
+        oss << locationInfo << ": Internal Catch error: '" << message << '\'';
         if( alwaysTrue() )
             throw std::logic_error( oss.str() );
     }
@@ -7943,8 +8506,6 @@ namespace Catch {
 // #included from: catch_debugger.hpp
 #define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED
 
-#include <iostream>
-
 #ifdef CATCH_PLATFORM_MAC
 
     #include <assert.h>
@@ -7993,6 +8554,33 @@ namespace Catch {
         }
     } // namespace Catch
 
+#elif defined(CATCH_PLATFORM_LINUX)
+    #include <fstream>
+    #include <string>
+
+    namespace Catch{
+        // The standard POSIX way of detecting a debugger is to attempt to
+        // ptrace() the process, but this needs to be done from a child and not
+        // this process itself to still allow attaching to this process later
+        // if wanted, so is rather heavy. Under Linux we have the PID of the
+        // "debugger" (which doesn't need to be gdb, of course, it could also
+        // be strace, for example) in /proc/$PID/status, so just get it from
+        // there instead.
+        bool isDebuggerActive(){
+            std::ifstream in("/proc/self/status");
+            for( std::string line; std::getline(in, line); ) {
+                static const int PREFIX_LEN = 11;
+                if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) {
+                    // We're traced if the PID is not 0 and no other PID starts
+                    // with 0 digit, so it's enough to check for just a single
+                    // character.
+                    return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0';
+                }
+            }
+
+            return false;
+        }
+    } // namespace Catch
 #elif defined(_MSC_VER)
     extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
     namespace Catch {
@@ -8014,7 +8602,7 @@ namespace Catch {
 #endif // Platform
 
 #ifdef CATCH_PLATFORM_WINDOWS
-    extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* );
+
     namespace Catch {
         void writeToDebugConsole( std::string const& text ) {
             ::OutputDebugStringA( text.c_str() );
@@ -8090,7 +8678,7 @@ std::string toString( std::string const& value ) {
             }
         }
     }
-    return "\"" + s + "\"";
+    return '"' + s + '"';
 }
 std::string toString( std::wstring const& value ) {
 
@@ -8111,19 +8699,19 @@ std::string toString( char* const value ) {
 
 std::string toString( const wchar_t* const value )
 {
-	return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" );
+    return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" );
 }
 
 std::string toString( wchar_t* const value )
 {
-	return Catch::toString( static_cast<const wchar_t*>( value ) );
+    return Catch::toString( static_cast<const wchar_t*>( value ) );
 }
 
 std::string toString( int value ) {
     std::ostringstream oss;
     oss << value;
     if( value > Detail::hexThreshold )
-        oss << " (0x" << std::hex << value << ")";
+        oss << " (0x" << std::hex << value << ')';
     return oss.str();
 }
 
@@ -8131,7 +8719,7 @@ std::string toString( unsigned long value ) {
     std::ostringstream oss;
     oss << value;
     if( value > Detail::hexThreshold )
-        oss << " (0x" << std::hex << value << ")";
+        oss << " (0x" << std::hex << value << ')';
     return oss.str();
 }
 
@@ -8159,7 +8747,7 @@ std::string toString( const double value ) {
     return fpToString( value, 10 );
 }
 std::string toString( const float value ) {
-    return fpToString( value, 5 ) + "f";
+    return fpToString( value, 5 ) + 'f';
 }
 
 std::string toString( bool value ) {
@@ -8167,9 +8755,19 @@ std::string toString( bool value ) {
 }
 
 std::string toString( char value ) {
-    return value < ' '
-        ? toString( static_cast<unsigned int>( value ) )
-        : Detail::makeString( value );
+    if ( value == '\r' )
+        return "'\\r'";
+    if ( value == '\f' )
+        return "'\\f'";
+    if ( value == '\n' )
+        return "'\\n'";
+    if ( value == '\t' )
+        return "'\\t'";
+    if ( '\0' <= value && value < ' ' )
+        return toString( static_cast<unsigned int>( value ) );
+    char chstr[] = "' '";
+    chstr[1] = value;
+    return chstr;
 }
 
 std::string toString( signed char value ) {
@@ -8185,14 +8783,14 @@ std::string toString( long long value ) {
     std::ostringstream oss;
     oss << value;
     if( value > Detail::hexThreshold )
-        oss << " (0x" << std::hex << value << ")";
+        oss << " (0x" << std::hex << value << ')';
     return oss.str();
 }
 std::string toString( unsigned long long value ) {
     std::ostringstream oss;
     oss << value;
     if( value > Detail::hexThreshold )
-        oss << " (0x" << std::hex << value << ")";
+        oss << " (0x" << std::hex << value << ')';
     return oss.str();
 }
 #endif
@@ -8249,22 +8847,10 @@ namespace Catch {
         m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed;
         return *this;
     }
-    ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) {
-        m_exprComponents.lhs = lhs;
-        return *this;
-    }
-    ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) {
-        m_exprComponents.rhs = rhs;
-        return *this;
-    }
-    ResultBuilder& ResultBuilder::setOp( std::string const& op ) {
-        m_exprComponents.op = op;
-        return *this;
-    }
 
-    void ResultBuilder::endExpression() {
-        m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition );
-        captureExpression();
+    void ResultBuilder::endExpression( DecomposedExpression const& expr ) {
+        AssertionResult result = build( expr );
+        handleResult( result );
     }
 
     void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) {
@@ -8277,16 +8863,17 @@ namespace Catch {
         setResultType( resultType );
         captureExpression();
     }
+
     void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) {
         if( expectedMessage.empty() )
-            captureExpectedException( Matchers::Impl::Generic::AllOf<std::string>() );
+            captureExpectedException( Matchers::Impl::MatchAllOf<std::string>() );
         else
             captureExpectedException( Matchers::Equals( expectedMessage ) );
     }
 
-    void ResultBuilder::captureExpectedException( Matchers::Impl::Matcher<std::string> const& matcher ) {
+    void ResultBuilder::captureExpectedException( Matchers::Impl::MatcherBase<std::string> const& matcher ) {
 
-        assert( m_exprComponents.testFalse == false );
+        assert( !isFalseTest( m_assertionInfo.resultDisposition ) );
         AssertionResultData data = m_data;
         data.resultType = ResultWas::Ok;
         data.reconstructedExpression = m_assertionInfo.capturedExpression;
@@ -8304,6 +8891,7 @@ namespace Catch {
         AssertionResult result = build();
         handleResult( result );
     }
+
     void ResultBuilder::handleResult( AssertionResult const& result )
     {
         getResultCapture().assertionEnded( result );
@@ -8315,7 +8903,17 @@ namespace Catch {
                 m_shouldThrow = true;
         }
     }
+
     void ResultBuilder::react() {
+#if defined(CATCH_CONFIG_FAST_COMPILE)
+        if (m_shouldDebugBreak) {
+            ///////////////////////////////////////////////////////////////////
+            // To inspect the state during test, you need to go one level up the callstack
+            // To go back to the test and change execution, jump over the throw statement
+            ///////////////////////////////////////////////////////////////////
+            CATCH_BREAK_INTO_DEBUGGER();
+        }
+#endif
         if( m_shouldThrow )
             throw Catch::TestFailureException();
     }
@@ -8325,43 +8923,32 @@ namespace Catch {
 
     AssertionResult ResultBuilder::build() const
     {
-        assert( m_data.resultType != ResultWas::Unknown );
+        return build( *this );
+    }
 
+    // CAVEAT: The returned AssertionResult stores a pointer to the argument expr,
+    //         a temporary DecomposedExpression, which in turn holds references to
+    //         operands, possibly temporary as well.
+    //         It should immediately be passed to handleResult; if the expression
+    //         needs to be reported, its string expansion must be composed before
+    //         the temporaries are destroyed.
+    AssertionResult ResultBuilder::build( DecomposedExpression const& expr ) const
+    {
+        assert( m_data.resultType != ResultWas::Unknown );
         AssertionResultData data = m_data;
 
-        // Flip bool results if testFalse is set
-        if( m_exprComponents.testFalse ) {
-            if( data.resultType == ResultWas::Ok )
-                data.resultType = ResultWas::ExpressionFailed;
-            else if( data.resultType == ResultWas::ExpressionFailed )
-                data.resultType = ResultWas::Ok;
+        // Flip bool results if FalseTest flag is set
+        if( isFalseTest( m_assertionInfo.resultDisposition ) ) {
+            data.negate( expr.isBinaryExpression() );
         }
 
         data.message = m_stream.oss.str();
-        data.reconstructedExpression = reconstructExpression();
-        if( m_exprComponents.testFalse ) {
-            if( m_exprComponents.op == "" )
-                data.reconstructedExpression = "!" + data.reconstructedExpression;
-            else
-                data.reconstructedExpression = "!(" + data.reconstructedExpression + ")";
-        }
+        data.decomposedExpression = &expr; // for lazy reconstruction
         return AssertionResult( m_assertionInfo, data );
     }
-    std::string ResultBuilder::reconstructExpression() const {
-        if( m_exprComponents.op == "" )
-            return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs;
-        else if( m_exprComponents.op == "matches" )
-            return m_exprComponents.lhs + " " + m_exprComponents.rhs;
-        else if( m_exprComponents.op != "!" ) {
-            if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 &&
-                m_exprComponents.lhs.find("\n") == std::string::npos &&
-                m_exprComponents.rhs.find("\n") == std::string::npos )
-                return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs;
-            else
-                return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs;
-        }
-        else
-            return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}";
+
+    void ResultBuilder::reconstructExpression( std::string& dest ) const {
+        dest = m_assertionInfo.capturedExpression;
     }
 
 } // end namespace Catch
@@ -8390,9 +8977,6 @@ namespace Catch {
 
 } // end namespace Catch
 
-#include <map>
-#include <iostream>
-
 namespace Catch {
 
     TagAliasRegistry::~TagAliasRegistry() {}
@@ -8422,7 +9006,7 @@ namespace Catch {
 
     void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) {
 
-        if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) {
+        if( !startsWith( alias, "[@" ) || !endsWith( alias, ']' ) ) {
             std::ostringstream oss;
             oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo;
             throw std::domain_error( oss.str().c_str() );
@@ -8430,7 +9014,7 @@ namespace Catch {
         if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) {
             std::ostringstream oss;
             oss << "error: tag alias, \"" << alias << "\" already registered.\n"
-                << "\tFirst seen at " << find(alias)->lineInfo << "\n"
+                << "\tFirst seen at " << find(alias)->lineInfo << '\n'
                 << "\tRedefined at " << lineInfo;
             throw std::domain_error( oss.str().c_str() );
         }
@@ -8458,6 +9042,86 @@ namespace Catch {
 
 } // end namespace Catch
 
+// #included from: catch_matchers_string.hpp
+
+namespace Catch {
+namespace Matchers {
+
+    namespace StdString {
+
+        CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity )
+        :   m_caseSensitivity( caseSensitivity ),
+            m_str( adjustString( str ) )
+        {}
+        std::string CasedString::adjustString( std::string const& str ) const {
+            return m_caseSensitivity == CaseSensitive::No
+                   ? toLower( str )
+                   : str;
+        }
+        std::string CasedString::caseSensitivitySuffix() const {
+            return m_caseSensitivity == CaseSensitive::No
+                   ? " (case insensitive)"
+                   : std::string();
+        }
+
+        StringMatcherBase::StringMatcherBase( std::string operation, CasedString const& comparator )
+        : m_comparator( comparator ),
+          m_operation( operation ) {
+        }
+
+        std::string StringMatcherBase::describe() const {
+            std::string description;
+            description.reserve(5 + m_operation.size() + m_comparator.m_str.size() +
+                                        m_comparator.caseSensitivitySuffix().size());
+            description += m_operation;
+            description += ": \"";
+            description += m_comparator.m_str;
+            description += "\"";
+            description += m_comparator.caseSensitivitySuffix();
+            return description;
+        }
+
+        EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {}
+
+        bool EqualsMatcher::match( std::string const& source ) const {
+            return m_comparator.adjustString( source ) == m_comparator.m_str;
+        }
+
+        ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {}
+
+        bool ContainsMatcher::match( std::string const& source ) const {
+            return contains( m_comparator.adjustString( source ), m_comparator.m_str );
+        }
+
+        StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {}
+
+        bool StartsWithMatcher::match( std::string const& source ) const {
+            return startsWith( m_comparator.adjustString( source ), m_comparator.m_str );
+        }
+
+        EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {}
+
+        bool EndsWithMatcher::match( std::string const& source ) const {
+            return endsWith( m_comparator.adjustString( source ), m_comparator.m_str );
+        }
+
+    } // namespace StdString
+
+    StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
+        return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) );
+    }
+    StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
+        return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) );
+    }
+    StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
+        return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) );
+    }
+    StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) {
+        return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) );
+    }
+
+} // namespace Matchers
+} // namespace Catch
 // #included from: ../reporters/catch_reporter_multi.hpp
 #define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED
 
@@ -8601,6 +9265,7 @@ Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingRepo
 #define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED
 
 #include <cstring>
+#include <assert.h>
 
 namespace Catch {
 
@@ -8698,12 +9363,12 @@ namespace Catch {
 
         struct BySectionInfo {
             BySectionInfo( SectionInfo const& other ) : m_other( other ) {}
-			BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {}
+            BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {}
             bool operator() ( Ptr<SectionNode> const& node ) const {
                 return node->stats.sectionInfo.lineInfo == m_other.lineInfo;
             }
         private:
-			void operator=( BySectionInfo const& );
+            void operator=( BySectionInfo const& );
             SectionInfo const& m_other;
         };
 
@@ -8759,6 +9424,12 @@ namespace Catch {
             assert( !m_sectionStack.empty() );
             SectionNode& sectionNode = *m_sectionStack.back();
             sectionNode.assertions.push_back( assertionStats );
+            // AssertionResult holds a pointer to a temporary DecomposedExpression,
+            // which getExpandedExpression() calls to build the expression string.
+            // Our section stack copy of the assertionResult will likely outlive the
+            // temporary, so it must be expanded or discarded now to avoid calling
+            // a destroyed object later.
+            prepareExpandedExpression( sectionNode.assertions.back().assertionResult );
             return true;
         }
         virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE {
@@ -8793,6 +9464,13 @@ namespace Catch {
 
         virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {}
 
+        virtual void prepareExpandedExpression( AssertionResult& result ) const {
+            if( result.isOk() )
+                result.discardDecomposedExpression();
+            else
+                result.expandDecomposedExpression();
+        }
+
         Ptr<IConfig const> m_config;
         std::ostream& stream;
         std::vector<AssertionStats> m_assertions;
@@ -8813,7 +9491,7 @@ namespace Catch {
     char const* getLineOfChars() {
         static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0};
         if( !*line ) {
-            memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 );
+            std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 );
             line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0;
         }
         return line;
@@ -8898,7 +9576,7 @@ namespace Catch {
                 return new T( config );
             }
             virtual std::string getDescription() const {
-                return "";
+                return std::string();
             }
         };
 
@@ -8967,8 +9645,11 @@ namespace Catch {
                     default:
                         // Escape control chars - based on contribution by @espenalb in PR #465 and
                         // by @mrpi PR #588
-                        if ( ( c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' )
-                            os << "&#x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>( c ) << ';';
+                        if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) {
+                            // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
+                            os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
+                               << static_cast<int>( c );
+                        }
                         else
                             os << c;
                 }
@@ -9022,20 +9703,17 @@ namespace Catch {
         XmlWriter()
         :   m_tagIsOpen( false ),
             m_needsNewline( false ),
-            m_os( &Catch::cout() )
+            m_os( Catch::cout() )
         {
-            // We encode control characters, which requires
-            // XML 1.1
-            // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
-            *m_os << "<?xml version=\"1.1\" encoding=\"UTF-8\"?>\n";
+            writeDeclaration();
         }
 
         XmlWriter( std::ostream& os )
         :   m_tagIsOpen( false ),
             m_needsNewline( false ),
-            m_os( &os )
+            m_os( os )
         {
-            *m_os << "<?xml version=\"1.1\" encoding=\"UTF-8\"?>\n";
+            writeDeclaration();
         }
 
         ~XmlWriter() {
@@ -9046,7 +9724,7 @@ namespace Catch {
         XmlWriter& startElement( std::string const& name ) {
             ensureTagClosed();
             newlineIfNecessary();
-            stream() << m_indent << "<" << name;
+            m_os << m_indent << '<' << name;
             m_tags.push_back( name );
             m_indent += "  ";
             m_tagIsOpen = true;
@@ -9063,24 +9741,25 @@ namespace Catch {
             newlineIfNecessary();
             m_indent = m_indent.substr( 0, m_indent.size()-2 );
             if( m_tagIsOpen ) {
-                stream() << "/>\n";
+                m_os << "/>";
                 m_tagIsOpen = false;
             }
             else {
-                stream() << m_indent << "</" << m_tags.back() << ">\n";
+                m_os << m_indent << "</" << m_tags.back() << ">";
             }
+            m_os << std::endl;
             m_tags.pop_back();
             return *this;
         }
 
         XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) {
             if( !name.empty() && !attribute.empty() )
-                stream() << " " << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << "\"";
+                m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
             return *this;
         }
 
         XmlWriter& writeAttribute( std::string const& name, bool attribute ) {
-            stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\"";
+            m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"';
             return *this;
         }
 
@@ -9096,8 +9775,8 @@ namespace Catch {
                 bool tagWasOpen = m_tagIsOpen;
                 ensureTagClosed();
                 if( tagWasOpen && indent )
-                    stream() << m_indent;
-                stream() << XmlEncode( text );
+                    m_os << m_indent;
+                m_os << XmlEncode( text );
                 m_needsNewline = true;
             }
             return *this;
@@ -9105,39 +9784,39 @@ namespace Catch {
 
         XmlWriter& writeComment( std::string const& text ) {
             ensureTagClosed();
-            stream() << m_indent << "<!--" << text << "-->";
+            m_os << m_indent << "<!--" << text << "-->";
             m_needsNewline = true;
             return *this;
         }
 
+        void writeStylesheetRef( std::string const& url ) {
+            m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n";
+        }
+
         XmlWriter& writeBlankLine() {
             ensureTagClosed();
-            stream() << "\n";
+            m_os << '\n';
             return *this;
         }
 
-        void setStream( std::ostream& os ) {
-            m_os = &os;
+        void ensureTagClosed() {
+            if( m_tagIsOpen ) {
+                m_os << ">" << std::endl;
+                m_tagIsOpen = false;
+            }
         }
 
     private:
         XmlWriter( XmlWriter const& );
         void operator=( XmlWriter const& );
 
-        std::ostream& stream() {
-            return *m_os;
-        }
-
-        void ensureTagClosed() {
-            if( m_tagIsOpen ) {
-                stream() << ">\n";
-                m_tagIsOpen = false;
-            }
+        void writeDeclaration() {
+            m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
         }
 
         void newlineIfNecessary() {
             if( m_needsNewline ) {
-                stream() << "\n";
+                m_os << std::endl;
                 m_needsNewline = false;
             }
         }
@@ -9146,7 +9825,7 @@ namespace Catch {
         bool m_needsNewline;
         std::vector<std::string> m_tags;
         std::string m_indent;
-        std::ostream* m_os;
+        std::ostream& m_os;
     };
 
 }
@@ -9182,6 +9861,16 @@ namespace Catch {
             return "Reports test results as an XML document";
         }
 
+        virtual std::string getStylesheetRef() const {
+            return std::string();
+        }
+
+        void writeSourceInfo( SourceLineInfo const& sourceInfo ) {
+            m_xml
+                .writeAttribute( "filename", sourceInfo.file )
+                .writeAttribute( "line", sourceInfo.line );
+        }
+
     public: // StreamingReporterBase
 
         virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE {
@@ -9190,6 +9879,9 @@ namespace Catch {
 
         virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE {
             StreamingReporterBase::testRunStarting( testInfo );
+            std::string stylesheetRef = getStylesheetRef();
+            if( !stylesheetRef.empty() )
+                m_xml.writeStylesheetRef( stylesheetRef );
             m_xml.startElement( "Catch" );
             if( !m_config->name().empty() )
                 m_xml.writeAttribute( "name", m_config->name() );
@@ -9203,10 +9895,16 @@ namespace Catch {
 
         virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE {
             StreamingReporterBase::testCaseStarting(testInfo);
-            m_xml.startElement( "TestCase" ).writeAttribute( "name", testInfo.name );
+            m_xml.startElement( "TestCase" )
+                .writeAttribute( "name", trim( testInfo.name ) )
+                .writeAttribute( "description", testInfo.description )
+                .writeAttribute( "tags", testInfo.tagsAsString );
+
+            writeSourceInfo( testInfo.lineInfo );
 
             if ( m_config->showDurations() == ShowDurations::Always )
                 m_testCaseTimer.start();
+            m_xml.ensureTagClosed();
         }
 
         virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE {
@@ -9215,6 +9913,8 @@ namespace Catch {
                 m_xml.startElement( "Section" )
                     .writeAttribute( "name", trim( sectionInfo.name ) )
                     .writeAttribute( "description", sectionInfo.description );
+                writeSourceInfo( sectionInfo.lineInfo );
+                m_xml.ensureTagClosed();
             }
         }
 
@@ -9246,9 +9946,9 @@ namespace Catch {
             if( assertionResult.hasExpression() ) {
                 m_xml.startElement( "Expression" )
                     .writeAttribute( "success", assertionResult.succeeded() )
-					.writeAttribute( "type", assertionResult.getTestMacroName() )
-                    .writeAttribute( "filename", assertionResult.getSourceInfo().file )
-                    .writeAttribute( "line", assertionResult.getSourceInfo().line );
+                    .writeAttribute( "type", assertionResult.getTestMacroName() );
+
+                writeSourceInfo( assertionResult.getSourceInfo() );
 
                 m_xml.scopedElement( "Original" )
                     .writeText( assertionResult.getExpression() );
@@ -9259,16 +9959,16 @@ namespace Catch {
             // And... Print a result applicable to each result type.
             switch( assertionResult.getResultType() ) {
                 case ResultWas::ThrewException:
-                    m_xml.scopedElement( "Exception" )
-                        .writeAttribute( "filename", assertionResult.getSourceInfo().file )
-                        .writeAttribute( "line", assertionResult.getSourceInfo().line )
-                        .writeText( assertionResult.getMessage() );
+                    m_xml.startElement( "Exception" );
+                    writeSourceInfo( assertionResult.getSourceInfo() );
+                    m_xml.writeText( assertionResult.getMessage() );
+                    m_xml.endElement();
                     break;
                 case ResultWas::FatalErrorCondition:
-                    m_xml.scopedElement( "FatalErrorCondition" )
-                        .writeAttribute( "filename", assertionResult.getSourceInfo().file )
-                        .writeAttribute( "line", assertionResult.getSourceInfo().line )
-                        .writeText( assertionResult.getMessage() );
+                    m_xml.startElement( "FatalErrorCondition" );
+                    writeSourceInfo( assertionResult.getSourceInfo() );
+                    m_xml.writeText( assertionResult.getMessage() );
+                    m_xml.endElement();
                     break;
                 case ResultWas::Info:
                     m_xml.scopedElement( "Info" )
@@ -9278,8 +9978,10 @@ namespace Catch {
                     // Warning will already have been written
                     break;
                 case ResultWas::ExplicitFailure:
-                    m_xml.scopedElement( "Failure" )
-                        .writeText( assertionResult.getMessage() );
+                    m_xml.startElement( "Failure" );
+                    writeSourceInfo( assertionResult.getSourceInfo() );
+                    m_xml.writeText( assertionResult.getMessage() );
+                    m_xml.endElement();
                     break;
                 default:
                     break;
@@ -9314,6 +10016,11 @@ namespace Catch {
             if ( m_config->showDurations() == ShowDurations::Always )
                 e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() );
 
+            if( !testCaseStats.stdOut.empty() )
+                m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false );
+            if( !testCaseStats.stdErr.empty() )
+                m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false );
+
             m_xml.endElement();
         }
 
@@ -9353,6 +10060,35 @@ namespace Catch {
 
 namespace Catch {
 
+    namespace {
+        std::string getCurrentTimestamp() {
+            // Beware, this is not reentrant because of backward compatibility issues
+            // Also, UTC only, again because of backward compatibility (%z is C++11)
+            time_t rawtime;
+            std::time(&rawtime);
+            const size_t timeStampSize = sizeof("2017-01-16T17:06:45Z");
+
+#ifdef _MSC_VER
+            std::tm timeInfo = {};
+            gmtime_s(&timeInfo, &rawtime);
+#else
+            std::tm* timeInfo;
+            timeInfo = std::gmtime(&rawtime);
+#endif
+
+            char timeStamp[timeStampSize];
+            const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
+
+#ifdef _MSC_VER
+            std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
+#else
+            std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
+#endif
+            return std::string(timeStamp);
+        }
+
+    }
+
     class JunitReporter : public CumulativeReporterBase {
     public:
         JunitReporter( ReporterConfig const& _config )
@@ -9417,7 +10153,7 @@ namespace Catch {
                 xml.writeAttribute( "time", "" );
             else
                 xml.writeAttribute( "time", suiteTime );
-            xml.writeAttribute( "timestamp", "tbd" ); // !TBD
+            xml.writeAttribute( "timestamp", getCurrentTimestamp() );
 
             // Write test cases
             for( TestGroupNode::ChildNodes::const_iterator
@@ -9452,7 +10188,7 @@ namespace Catch {
                             SectionNode const& sectionNode ) {
             std::string name = trim( sectionNode.stats.sectionInfo.name );
             if( !rootName.empty() )
-                name = rootName + "/" + name;
+                name = rootName + '/' + name;
 
             if( !sectionNode.assertions.empty() ||
                 !sectionNode.stdOut.empty() ||
@@ -9530,14 +10266,14 @@ namespace Catch {
 
                 std::ostringstream oss;
                 if( !result.getMessage().empty() )
-                    oss << result.getMessage() << "\n";
+                    oss << result.getMessage() << '\n';
                 for( std::vector<MessageInfo>::const_iterator
                         it = stats.infoMessages.begin(),
                         itEnd = stats.infoMessages.end();
                             it != itEnd;
                             ++it )
                     if( it->type == ResultWas::Info )
-                        oss << it->message << "\n";
+                        oss << it->message << '\n';
 
                 oss << "at " << result.getSourceInfo();
                 xml.writeText( oss.str(), false );
@@ -9558,8 +10294,30 @@ namespace Catch {
 // #included from: ../reporters/catch_reporter_console.hpp
 #define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED
 
+#include <cfloat>
+#include <cstdio>
+
 namespace Catch {
 
+    namespace {
+        // Because formatting using c++ streams is stateful, drop down to C is required
+        // Alternatively we could use stringstream, but its performance is... not good.
+        std::string getFormattedDuration( double duration ) {
+            // Max exponent + 1 is required to represent the whole part
+            // + 1 for decimal point
+            // + 3 for the 3 decimal places
+            // + 1 for null terminator
+            const size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1;
+            char buffer[maxDoubleSize];
+#ifdef _MSC_VER
+            sprintf_s(buffer, "%.3f", duration);
+#else
+            sprintf(buffer, "%.3f", duration);
+#endif
+            return std::string(buffer);
+        }
+    }
+
     struct ConsoleReporter : StreamingReporterBase {
         ConsoleReporter( ReporterConfig const& _config )
         :   StreamingReporterBase( _config ),
@@ -9572,7 +10330,7 @@ namespace Catch {
         }
 
         virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE {
-            stream << "No test cases matched '" << spec << "'" << std::endl;
+            stream << "No test cases matched '" << spec << '\'' << std::endl;
         }
 
         virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {
@@ -9612,15 +10370,12 @@ namespace Catch {
                     stream << "\nNo assertions in test case";
                 stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl;
             }
+            if( m_config->showDurations() == ShowDurations::Always ) {
+                stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl;
+            }
             if( m_headerPrinted ) {
-                if( m_config->showDurations() == ShowDurations::Always )
-                    stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl;
                 m_headerPrinted = false;
             }
-            else {
-                if( m_config->showDurations() == ShowDurations::Always )
-                    stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl;
-            }
             StreamingReporterBase::sectionEnded( _sectionStats );
         }
 
@@ -9633,7 +10388,7 @@ namespace Catch {
                 printSummaryDivider();
                 stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n";
                 printTotals( _testGroupStats.totals );
-                stream << "\n" << std::endl;
+                stream << '\n' << std::endl;
             }
             StreamingReporterBase::testGroupEnded( _testGroupStats );
         }
@@ -9725,13 +10480,13 @@ namespace Catch {
                 printSourceInfo();
                 if( stats.totals.assertions.total() > 0 ) {
                     if( result.isOk() )
-                        stream << "\n";
+                        stream << '\n';
                     printResultType();
                     printOriginalExpression();
                     printReconstructedExpression();
                 }
                 else {
-                    stream << "\n";
+                    stream << '\n';
                 }
                 printMessage();
             }
@@ -9748,25 +10503,25 @@ namespace Catch {
                     Colour colourGuard( Colour::OriginalExpression );
                     stream  << "  ";
                     stream << result.getExpressionInMacro();
-                    stream << "\n";
+                    stream << '\n';
                 }
             }
             void printReconstructedExpression() const {
                 if( result.hasExpandedExpression() ) {
                     stream << "with expansion:\n";
                     Colour colourGuard( Colour::ReconstructedExpression );
-                    stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n";
+                    stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << '\n';
                 }
             }
             void printMessage() const {
                 if( !messageLabel.empty() )
-                    stream << messageLabel << ":" << "\n";
+                    stream << messageLabel << ':' << '\n';
                 for( std::vector<MessageInfo>::const_iterator it = messages.begin(), itEnd = messages.end();
                         it != itEnd;
                         ++it ) {
                     // If this assertion is a warning ignore any INFO messages
                     if( printInfoMessages || it->type != ResultWas::Info )
-                        stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n";
+                        stream << Text( it->message, TextAttributes().setIndent(2) ) << '\n';
                 }
             }
             void printSourceInfo() const {
@@ -9798,7 +10553,7 @@ namespace Catch {
             }
         }
         void lazyPrintRunInfo() {
-            stream  << "\n" << getLineOfChars<'~'>() << "\n";
+            stream  << '\n' << getLineOfChars<'~'>() << '\n';
             Colour colour( Colour::SecondaryText );
             stream  << currentTestRunInfo->name
                     << " is a Catch v"  << libraryVersion << " host application.\n"
@@ -9829,22 +10584,22 @@ namespace Catch {
                     printHeaderString( it->name, 2 );
             }
 
-            SourceLineInfo lineInfo = m_sectionStack.front().lineInfo;
+            SourceLineInfo lineInfo = m_sectionStack.back().lineInfo;
 
             if( !lineInfo.empty() ){
-                stream << getLineOfChars<'-'>() << "\n";
+                stream << getLineOfChars<'-'>() << '\n';
                 Colour colourGuard( Colour::FileName );
-                stream << lineInfo << "\n";
+                stream << lineInfo << '\n';
             }
-            stream << getLineOfChars<'.'>() << "\n" << std::endl;
+            stream << getLineOfChars<'.'>() << '\n' << std::endl;
         }
 
         void printClosedHeader( std::string const& _name ) {
             printOpenHeader( _name );
-            stream << getLineOfChars<'.'>() << "\n";
+            stream << getLineOfChars<'.'>() << '\n';
         }
         void printOpenHeader( std::string const& _name ) {
-            stream  << getLineOfChars<'-'>() << "\n";
+            stream  << getLineOfChars<'-'>() << '\n';
             {
                 Colour colourGuard( Colour::Headers );
                 printHeaderString( _name );
@@ -9861,7 +10616,7 @@ namespace Catch {
                 i = 0;
             stream << Text( _string, TextAttributes()
                                         .setIndent( indent+i)
-                                        .setInitialIndent( indent ) ) << "\n";
+                                        .setInitialIndent( indent ) ) << '\n';
         }
 
         struct SummaryColumn {
@@ -9876,9 +10631,9 @@ namespace Catch {
                 std::string row = oss.str();
                 for( std::vector<std::string>::iterator it = rows.begin(); it != rows.end(); ++it ) {
                     while( it->size() < row.size() )
-                        *it = " " + *it;
+                        *it = ' ' + *it;
                     while( it->size() > row.size() )
-                        row = " " + row;
+                        row = ' ' + row;
                 }
                 rows.push_back( row );
                 return *this;
@@ -9898,8 +10653,8 @@ namespace Catch {
                 stream << Colour( Colour::ResultSuccess ) << "All tests passed";
                 stream << " ("
                         << pluralise( totals.assertions.passed, "assertion" ) << " in "
-                        << pluralise( totals.testCases.passed, "test case" ) << ")"
-                        << "\n";
+                        << pluralise( totals.testCases.passed, "test case" ) << ')'
+                        << '\n';
             }
             else {
 
@@ -9934,10 +10689,10 @@ namespace Catch {
                 else if( value != "0" ) {
                     stream  << Colour( Colour::LightGrey ) << " | ";
                     stream  << Colour( it->colour )
-                            << value << " " << it->label;
+                            << value << ' ' << it->label;
                 }
             }
-            stream << "\n";
+            stream << '\n';
         }
 
         static std::size_t makeRatio( std::size_t number, std::size_t total ) {
@@ -9973,10 +10728,10 @@ namespace Catch {
             else {
                 stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' );
             }
-            stream << "\n";
+            stream << '\n';
         }
         void printSummaryDivider() {
-            stream << getLineOfChars<'-'>() << "\n";
+            stream << getLineOfChars<'-'>() << '\n';
         }
 
     private:
@@ -10011,7 +10766,7 @@ namespace Catch {
         }
 
         virtual void noMatchingTestCases( std::string const& spec ) {
-            stream << "No test cases matched '" << spec << "'" << std::endl;
+            stream << "No test cases matched '" << spec << '\'' << std::endl;
         }
 
         virtual void assertionStarting( AssertionInfo const& ) {
@@ -10038,7 +10793,7 @@ namespace Catch {
 
         virtual void testRunEnded( TestRunStats const& _testRunStats ) {
             printTotals( _testRunStats.totals );
-            stream << "\n" << std::endl;
+            stream << '\n' << std::endl;
             StreamingReporterBase::testRunEnded( _testRunStats );
         }
 
@@ -10138,26 +10893,26 @@ namespace Catch {
 
             void printSourceInfo() const {
                 Colour colourGuard( Colour::FileName );
-                stream << result.getSourceInfo() << ":";
+                stream << result.getSourceInfo() << ':';
             }
 
             void printResultType( Colour::Code colour, std::string passOrFail ) const {
                 if( !passOrFail.empty() ) {
                     {
                         Colour colourGuard( colour );
-                        stream << " " << passOrFail;
+                        stream << ' ' << passOrFail;
                     }
-                    stream << ":";
+                    stream << ':';
                 }
             }
 
             void printIssue( std::string issue ) const {
-                stream << " " << issue;
+                stream << ' ' << issue;
             }
 
             void printExpressionWas() {
                 if( result.hasExpression() ) {
-                    stream << ";";
+                    stream << ';';
                     {
                         Colour colour( dimColour() );
                         stream << " expression was:";
@@ -10168,7 +10923,7 @@ namespace Catch {
 
             void printOriginalExpression() const {
                 if( result.hasExpression() ) {
-                    stream << " " << result.getExpression();
+                    stream << ' ' << result.getExpression();
                 }
             }
 
@@ -10184,7 +10939,7 @@ namespace Catch {
 
             void printMessage() {
                 if ( itMessage != messages.end() ) {
-                    stream << " '" << itMessage->message << "'";
+                    stream << " '" << itMessage->message << '\'';
                     ++itMessage;
                 }
             }
@@ -10199,13 +10954,13 @@ namespace Catch {
 
                 {
                     Colour colourGuard( colour );
-                    stream << " with " << pluralise( N, "message" ) << ":";
+                    stream << " with " << pluralise( N, "message" ) << ':';
                 }
 
                 for(; itMessage != itEnd; ) {
                     // If this assertion is a warning ignore any INFO messages
                     if( printInfoMessages || itMessage->type != ResultWas::Info ) {
-                        stream << " '" << itMessage->message << "'";
+                        stream << " '" << itMessage->message << '\'';
                         if ( ++itMessage != itEnd ) {
                             Colour colourGuard( dimColour() );
                             stream << " and";
@@ -10231,7 +10986,7 @@ namespace Catch {
         // - green: Passed [both/all] N tests cases with M assertions.
 
         std::string bothOrAll( std::size_t count ) const {
-            return count == 1 ? "" : count == 2 ? "both " : "all " ;
+            return count == 1 ? std::string() : count == 2 ? "both " : "all " ;
         }
 
         void printTotals( const Totals& totals ) const {
@@ -10242,12 +10997,12 @@ namespace Catch {
                 Colour colour( Colour::ResultError );
                 const std::string qualify_assertions_failed =
                     totals.assertions.failed == totals.assertions.total() ?
-                        bothOrAll( totals.assertions.failed ) : "";
+                        bothOrAll( totals.assertions.failed ) : std::string();
                 stream <<
                     "Failed " << bothOrAll( totals.testCases.failed )
                               << pluralise( totals.testCases.failed, "test case"  ) << ", "
                     "failed " << qualify_assertions_failed <<
-                                 pluralise( totals.assertions.failed, "assertion" ) << ".";
+                                 pluralise( totals.assertions.failed, "assertion" ) << '.';
             }
             else if( totals.assertions.total() == 0 ) {
                 stream <<
@@ -10259,14 +11014,14 @@ namespace Catch {
                 Colour colour( Colour::ResultError );
                 stream <<
                     "Failed " << pluralise( totals.testCases.failed, "test case"  ) << ", "
-                    "failed " << pluralise( totals.assertions.failed, "assertion" ) << ".";
+                    "failed " << pluralise( totals.assertions.failed, "assertion" ) << '.';
             }
             else {
                 Colour colour( Colour::ResultSuccess );
                 stream <<
                     "Passed " << bothOrAll( totals.testCases.passed )
                               << pluralise( totals.testCases.passed, "test case"  ) <<
-                    " with "  << pluralise( totals.assertions.passed, "assertion" ) << ".";
+                    " with "  << pluralise( totals.assertions.passed, "assertion" ) << '.';
             }
         }
     };
@@ -10323,11 +11078,6 @@ namespace Catch {
     TestSpec::TagPattern::~TagPattern() {}
     TestSpec::ExcludedPattern::~ExcludedPattern() {}
 
-    Matchers::Impl::StdString::Equals::~Equals() {}
-    Matchers::Impl::StdString::Contains::~Contains() {}
-    Matchers::Impl::StdString::StartsWith::~StartsWith() {}
-    Matchers::Impl::StdString::EndsWith::~EndsWith() {}
-
     void Config::dummy() {}
 
     namespace TestCaseTracking {
@@ -10352,7 +11102,8 @@ namespace Catch {
 
 // Standard C/C++ main entry point
 int main (int argc, char * argv[]) {
-    return Catch::Session().run( argc, argv );
+	int result = Catch::Session().run( argc, argv );
+    return ( result < 0xff ? result : 0xff );
 }
 
 #else // __OBJC__
@@ -10370,7 +11121,7 @@ int main (int argc, char * const argv[]) {
     [pool drain];
 #endif
 
-    return result;
+    return ( result < 0xff ? result : 0xff );
 }
 
 #endif // __OBJC__
@@ -10400,12 +11151,12 @@ int main (int argc, char * const argv[]) {
 #define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" )
 #define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" )
 
-#define CATCH_CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" )
+#define CATCH_CHECK_THROWS( expr )  INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CATCH_CHECK_THROWS" )
 #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" )
 #define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CATCH_CHECK_THROWS_WITH" )
 #define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" )
 
-#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" )
+#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" )
 #define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" )
 
 #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" )
diff --git a/test/merge/CMakeLists.txt b/test/merge/CMakeLists.txt
index 5e59484..1ba4845 100644
--- a/test/merge/CMakeLists.txt
+++ b/test/merge/CMakeLists.txt
@@ -2,7 +2,7 @@
 #
 #  CMake Config
 #
-#  Osmium Tool Tests - getid
+#  Osmium Tool Tests - merge
 #
 #-----------------------------------------------------------------------------
 
diff --git a/test/tags-filter/CMakeLists.txt b/test/tags-filter/CMakeLists.txt
new file mode 100644
index 0000000..c501c99
--- /dev/null
+++ b/test/tags-filter/CMakeLists.txt
@@ -0,0 +1,30 @@
+#-----------------------------------------------------------------------------
+#
+#  CMake Config
+#
+#  Osmium Tool Tests - tags-filter
+#
+#-----------------------------------------------------------------------------
+
+function(check_tags_filter_R _name _input _expressions _output)
+    check_output(tags-filter ${_name} "tags-filter --generator=test -f osm -R tags-filter/${_input} ${_expressions}" "tags-filter/${_output}")
+endfunction()
+
+function(check_tags_filter_i _name _input _expressions _output)
+    check_output(tags-filter ${_name} "tags-filter --generator=test -f osm -i -R tags-filter/${_input} ${_expressions}" "tags-filter/${_output}")
+endfunction()
+
+function(check_tags_filter _name _input _expressions _output)
+    check_output(tags-filter ${_name} "tags-filter --generator=test -f osm tags-filter/${_input} ${_expressions}" "tags-filter/${_output}")
+endfunction()
+
+check_tags_filter_R(node input.osm n/amenity output-amenity.osm)
+check_tags_filter_R(highway input.osm w/highway output-highway.osm)
+check_tags_filter_R(note input.osm note output-note.osm)
+
+check_tags_filter_i(note-i input.osm note output-no-note.osm)
+
+check_tags_filter(highway-r input.osm w/highway output-highway-r.osm)
+
+
+#-----------------------------------------------------------------------------
diff --git a/test/tags-filter/input.osm b/test/tags-filter/input.osm
new file mode 100644
index 0000000..dbc99b2
--- /dev/null
+++ b/test/tags-filter/input.osm
@@ -0,0 +1,30 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" upload="false" generator="testdata">
+  <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
+  <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
+  <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="1"/>
+  <node id="13" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
+  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="5" lon="1">
+    <tag k="amenity" v="post_box"/>
+  </node>
+  <node id="15" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="6" lon="1">
+    <tag k="highway" v="traffic_signals"/>
+  </node>
+  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="10"/>
+    <nd ref="11"/>
+    <nd ref="12"/>
+    <tag k="highway" v="primary"/>
+  </way>
+  <way id="21" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="12"/>
+    <nd ref="13"/>
+    <tag k="highway" v="residential"/>
+    <tag k="note" v="test"/>
+  </way>
+  <relation id="30" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="node" ref="12" role="m1"/>
+    <member type="way" ref="20" role="m2"/>
+    <tag k="note" v="test"/>
+  </relation>
+</osm>
diff --git a/test/tags-filter/output-amenity.osm b/test/tags-filter/output-amenity.osm
new file mode 100644
index 0000000..cb3790e
--- /dev/null
+++ b/test/tags-filter/output-amenity.osm
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="test">
+  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="5" lon="1">
+    <tag k="amenity" v="post_box"/>
+  </node>
+</osm>
diff --git a/test/tags-filter/output-highway-r.osm b/test/tags-filter/output-highway-r.osm
new file mode 100644
index 0000000..d347059
--- /dev/null
+++ b/test/tags-filter/output-highway-r.osm
@@ -0,0 +1,19 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="test">
+  <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
+  <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
+  <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="1"/>
+  <node id="13" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
+  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="10"/>
+    <nd ref="11"/>
+    <nd ref="12"/>
+    <tag k="highway" v="primary"/>
+  </way>
+  <way id="21" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="12"/>
+    <nd ref="13"/>
+    <tag k="highway" v="residential"/>
+    <tag k="note" v="test"/>
+  </way>
+</osm>
diff --git a/test/tags-filter/output-highway.osm b/test/tags-filter/output-highway.osm
new file mode 100644
index 0000000..18e1127
--- /dev/null
+++ b/test/tags-filter/output-highway.osm
@@ -0,0 +1,15 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="test">
+  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="10"/>
+    <nd ref="11"/>
+    <nd ref="12"/>
+    <tag k="highway" v="primary"/>
+  </way>
+  <way id="21" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="12"/>
+    <nd ref="13"/>
+    <tag k="highway" v="residential"/>
+    <tag k="note" v="test"/>
+  </way>
+</osm>
diff --git a/test/tags-filter/output-no-note.osm b/test/tags-filter/output-no-note.osm
new file mode 100644
index 0000000..b265efd
--- /dev/null
+++ b/test/tags-filter/output-no-note.osm
@@ -0,0 +1,19 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="test">
+  <node id="10" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="1" lon="1"/>
+  <node id="11" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="2" lon="1"/>
+  <node id="12" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="3" lon="1"/>
+  <node id="13" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="4" lon="1"/>
+  <node id="14" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="5" lon="1">
+    <tag k="amenity" v="post_box"/>
+  </node>
+  <node id="15" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1" lat="6" lon="1">
+    <tag k="highway" v="traffic_signals"/>
+  </node>
+  <way id="20" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="10"/>
+    <nd ref="11"/>
+    <nd ref="12"/>
+    <tag k="highway" v="primary"/>
+  </way>
+</osm>
diff --git a/test/tags-filter/output-note.osm b/test/tags-filter/output-note.osm
new file mode 100644
index 0000000..b7c07e1
--- /dev/null
+++ b/test/tags-filter/output-note.osm
@@ -0,0 +1,14 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<osm version="0.6" generator="test">
+  <way id="21" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <nd ref="12"/>
+    <nd ref="13"/>
+    <tag k="highway" v="residential"/>
+    <tag k="note" v="test"/>
+  </way>
+  <relation id="30" version="1" timestamp="2015-01-01T01:00:00Z" uid="1" user="test" changeset="1">
+    <member type="node" ref="12" role="m1"/>
+    <member type="way" ref="20" role="m2"/>
+    <tag k="note" v="test"/>
+  </relation>
+</osm>
diff --git a/test/tags-filter/test_unit.cpp b/test/tags-filter/test_unit.cpp
new file mode 100644
index 0000000..e8c4322
--- /dev/null
+++ b/test/tags-filter/test_unit.cpp
@@ -0,0 +1,57 @@
+
+#include <sstream>
+
+#include "test.hpp" // IWYU pragma: keep
+
+#include "command_tags_filter.hpp"
+#include "util.hpp"
+
+static void test_filter(const std::string& expression, osmium::osm_entity_bits::type entities, const char* filter) {
+    const auto p = get_filter_expression(expression);
+    REQUIRE(p.first == entities);
+    REQUIRE(p.second == filter);
+}
+
+TEST_CASE("Get tags filter expression") {
+    test_filter("highway", osmium::osm_entity_bits::nwr, "highway");
+    test_filter("/highway", osmium::osm_entity_bits::nwr, "highway");
+    test_filter("n/highway", osmium::osm_entity_bits::node, "highway");
+    test_filter("w/highway", osmium::osm_entity_bits::way, "highway");
+    test_filter("r/highway", osmium::osm_entity_bits::relation, "highway");
+    test_filter("nw/highway", osmium::osm_entity_bits::node | osmium::osm_entity_bits::way, "highway");
+    test_filter("n/highway/foo", osmium::osm_entity_bits::node, "highway/foo");
+    REQUIRE_THROWS_AS(get_filter_expression("highway/foo"), argument_error);
+}
+
+osmium::StringMatcher get_matcher(std::string string);
+
+void test_matcher(const char* string, const char* print_out) {
+    std::stringstream ss;
+    ss << get_matcher(string);
+    REQUIRE(ss.str() == print_out);
+}
+
+TEST_CASE("get_matcher") {
+    test_matcher("foo", "equal[foo]");
+    test_matcher("", "equal[]");
+    test_matcher("foo*", "prefix[foo]");
+    test_matcher(" foo* ", "prefix[foo]");
+    test_matcher("*foo", "substring[foo]");
+    test_matcher("*foo*", "substring[foo]");
+    test_matcher(" *foo* ", "substring[foo]");
+    test_matcher("*", "always_true");
+    test_matcher(" * ", "always_true");
+    test_matcher("f*oo", "equal[f*oo]");
+    test_matcher("foo,bar", "list[[foo][bar]]");
+    test_matcher("foo,bar*,baz", "list[[foo][bar*][baz]]");
+    test_matcher("*foo,bar", "substring[foo,bar]");
+    test_matcher("foo ", "equal[foo]");
+    test_matcher(" foo", "equal[foo]");
+    test_matcher(" foo ", "equal[foo]");
+    test_matcher("foo    ", "equal[foo]");
+    test_matcher("    foo", "equal[foo]");
+    test_matcher("  foo ", "equal[foo]");
+    test_matcher("foo, bar, baz", "list[[foo][bar][baz]]");
+    test_matcher("  foo, bar   ,baz   ", "list[[foo][bar][baz]]");
+}
+
diff --git a/test/util/test_unit.cpp b/test/util/test_unit.cpp
index 1718fdb..d72bc9e 100644
--- a/test/util/test_unit.cpp
+++ b/test/util/test_unit.cpp
@@ -23,3 +23,14 @@ TEST_CASE("Get suffixes from path with dots in the middle") {
     REQUIRE(get_filename_suffix("anything/../somewhere/foo.bar.baz") == "bar.baz");
 }
 
+TEST_CASE("Get object types") {
+    REQUIRE(get_types("") == osmium::osm_entity_bits::nothing);
+    REQUIRE(get_types("n") == osmium::osm_entity_bits::node);
+    REQUIRE(get_types("w") == osmium::osm_entity_bits::way);
+    REQUIRE(get_types("r") == osmium::osm_entity_bits::relation);
+    REQUIRE(get_types("nw") == (osmium::osm_entity_bits::node | osmium::osm_entity_bits::way));
+    REQUIRE(get_types("rw") == (osmium::osm_entity_bits::way | osmium::osm_entity_bits::relation));
+    REQUIRE_THROWS_AS(get_types("x"), argument_error);
+    REQUIRE_THROWS_AS(get_types("nwx"), argument_error);
+}
+
diff --git a/zsh_completion/_osmium b/zsh_completion/_osmium
index e3425cc..c6739e1 100644
--- a/zsh_completion/_osmium
+++ b/zsh_completion/_osmium
@@ -18,7 +18,7 @@ polygon_file_glob="'*.json *.geojson *.poly *.(osm|osh|osc|o5m|o5c|pbf|osm.pbf)
 
 _osmium() {
     local -a osmium_commands
-    osmium_commands=(add-locations-to-ways apply-changes cat diff changeset-filter check-refs derive-changes extract fileinfo getid help merge merge-changes renumber show sort time-filter)
+    osmium_commands=(add-locations-to-ways apply-changes cat diff changeset-filter check-refs derive-changes extract fileinfo getid help merge merge-changes renumber show sort tags-filter time-filter)
     if (( CURRENT > 2 )); then
         # Remember the subcommand name
         local cmd=${words[2]}
@@ -95,7 +95,10 @@ _osmium-apply-changes() {
         ${(f)"$(_osmium-multiple-inputs-options)"} \
         ${(f)"$(_osmium-output-format-options)"} \
         ${(f)"$(_osmium-output-options)"} \
-        '--with-history[update OSM history file]'
+        '--change-file-format[Format of the change file(s)]' \
+        '--locations-on-ways[update OSM file with locations on ways]' \
+        '(--with-history)-H[update OSM history file]' \
+        '(-H)--with-history[update OSM history file]'
 }
 
 _osmium-cat() {
@@ -195,7 +198,8 @@ _osmium-extract() {
         '(-s)--strategy[use strategy for computing extract]:extract strategy:_osmium_extract_strategy' \
         '*-S[set strategy option]:' \
         '*--option[set strategy option]:' \
-        '--with-history[input and output files are OSM history files]'
+        '(--with-history)-H[input and output files are OSM history files]' \
+        '(-H)--with-history[input and output files are OSM history files]'
 }
 
 _osmium-fileinfo() {
@@ -221,7 +225,7 @@ _osmium-getid() {
         ${(f)"$(_osmium-output-format-options)"} \
         ${(f)"$(_osmium-output-options)"} \
         '--default-type[default item type]' \
-        'i[read OSM IDs from text file]' \
+        '-i[read OSM IDs from text file]' \
         '--id-file[read OSM IDs from text file]' \
         '-I[read OSM IDs from OSM file]' \
         '--id-osm-file[read OSM IDs from OSM file]' \
@@ -230,7 +234,7 @@ _osmium-getid() {
         '(--with-history)-H[make it work with history files]' \
         '(-H)--with-history[make it work with history files]' \
         '(--progress)--no-progress[disable progress bar]' \
-        '(--no-progress)--progress[enable progress bar]'
+        '(--no-progress)--progress[enable progress bar]' \
         "*:IDs (format\: [nwr]ID):"
 }
 
@@ -261,7 +265,7 @@ _osmium-renumber() {
         '(--index-directory)-i[read/write index files in this directory]:directory:_path_files -/' \
         '(-i)--index-directory[read/write index files in this directory]:directory:_path_files -/' \
         '(--progress)--no-progress[disable progress bar]' \
-        '(--no-progress)--progress[enable progress bar]'
+        '(--no-progress)--progress[enable progress bar]' \
         '*-t[renumber only objects of given output types]:OSM entity type:_osmium_object_type' \
         '*--object-type[renumber only objects of given output types]:OSM entity type:_osmium_object_type'
 }
@@ -291,6 +295,21 @@ _osmium-sort() {
         ${(f)"$(_osmium-output-options)"}
 }
 
+_osmium-tags-filter() {
+    _arguments : \
+        ${(f)"$(_osmium-common-options)"} \
+        ${(f)"$(_osmium-single-input-options)"} \
+        ${(f)"$(_osmium-output-format-options)"} \
+        ${(f)"$(_osmium-output-options)"} \
+        '(--expressions)-e[read filter expressions from file]:filter expressions file:_files' \
+        '(-e)--expressions[read filter expressions from file]:filter expressions file:_files' \
+        '(--invert-match)-i[invert the sense of matching, exclude objects with matching tags]' \
+        '(-i)--invert-match[invert the sense of matching, exclude objects with matching tags]' \
+        '(--omit-referenced)-R[omit referenced objects]' \
+        '(-R)--omit-referenced[omit referenced objects]' \
+        "*:Filter expressions (format\: [nwr]*/key=[value]):"
+}
+
 _osmium-time-filter() {
     _arguments : \
         ${(f)"$(_osmium-common-options)"} \
@@ -377,7 +396,7 @@ _osmium_index_types() {
 
 _osmium-help() {
     local -a osmium_help_topics
-    osmium_help_topics=(add-locations-to-ways apply-changes cat diff changeset-filter check-refs derive-changes extract fileinfo getid help merge merge-changes renumber show sort time-filter file-formats)
+    osmium_help_topics=(add-locations-to-ways apply-changes cat diff changeset-filter check-refs derive-changes extract fileinfo getid help merge merge-changes renumber show sort tags-filter time-filter file-formats)
     _describe -t osmium-help-topics 'osmium help topics' osmium_help_topics
 }
 

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/osmium-tool.git



More information about the Pkg-grass-devel mailing list