mirror of
https://github.com/open-source-parsers/jsoncpp.git
synced 2025-10-14 15:05:34 +02:00
Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
15949af098 | ||
![]() |
8dc52b3cca | ||
![]() |
add941c1a9 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,4 +10,4 @@
|
||||
/libs/
|
||||
/doc/doxyfile
|
||||
/dist/
|
||||
#/include/json/version.h
|
||||
/include/json/version.h
|
||||
|
@@ -7,11 +7,12 @@ language: cpp
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
script: cmake -DJSONCPP_WITH_CMAKE_PACKAGE=$CMAKE_PKG -DJSONCPP_LIB_BUILD_SHARED=$SHARED_LIB -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_VERBOSE_MAKEFILE=$VERBOSE_MAKE . && make
|
||||
script: cmake -DJSONCPP_LIB_BUILD_SHARED=$SHARED_LIBRARY -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_VERBOSE_MAKEFILE=$VERBOSE_MAKE . && make
|
||||
env:
|
||||
matrix:
|
||||
- SHARED_LIB=ON STATIC_LIB=ON CMAKE_PKG=ON BUILD_TYPE=release VERBOSE_MAKE=false
|
||||
- SHARED_LIB=OFF STATIC_LIB=ON CMAKE_PKG=OFF BUILD_TYPE=debug VERBOSE_MAKE=true VERBOSE
|
||||
- SHARED_LIBRARY=ON BUILD_TYPE=release VERBOSE_MAKE=false
|
||||
- SHARED_LIBRARY=OFF BUILD_TYPE=release VERBOSE_MAKE=false
|
||||
- SHARED_LIBRARY=OFF BUILD_TYPE=debug VERBOSE VERBOSE_MAKE=true
|
||||
notifications:
|
||||
email:
|
||||
- aaronjjacobs@gmail.com
|
||||
|
@@ -17,20 +17,28 @@ IF(NOT WIN32)
|
||||
ENDIF(NOT CMAKE_BUILD_TYPE)
|
||||
ENDIF(NOT WIN32)
|
||||
|
||||
SET(LIB_SUFFIX "" CACHE STRING "Optional arch-dependent suffix for the library installation directory")
|
||||
|
||||
SET(RUNTIME_INSTALL_DIR bin
|
||||
CACHE PATH "Install dir for executables and dlls")
|
||||
SET(ARCHIVE_INSTALL_DIR lib${LIB_SUFFIX}
|
||||
SET(ARCHIVE_INSTALL_DIR lib
|
||||
CACHE PATH "Install dir for static libraries")
|
||||
SET(LIBRARY_INSTALL_DIR lib${LIB_SUFFIX}
|
||||
SET(LIBRARY_INSTALL_DIR lib
|
||||
CACHE PATH "Install dir for shared libraries")
|
||||
SET(INCLUDE_INSTALL_DIR include
|
||||
CACHE PATH "Install dir for headers")
|
||||
SET(PACKAGE_INSTALL_DIR lib${LIB_SUFFIX}/cmake
|
||||
SET(PACKAGE_INSTALL_DIR lib/cmake
|
||||
CACHE PATH "Install dir for cmake package config files")
|
||||
MARK_AS_ADVANCED( RUNTIME_INSTALL_DIR ARCHIVE_INSTALL_DIR INCLUDE_INSTALL_DIR PACKAGE_INSTALL_DIR )
|
||||
|
||||
# This ensures shared DLL are in the same dir as executable on Windows.
|
||||
# Put all executables / libraries are in a project global directory.
|
||||
SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
|
||||
CACHE PATH "Single directory for all static libraries.")
|
||||
SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
|
||||
CACHE PATH "Single directory for all dynamic libraries on Unix.")
|
||||
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin
|
||||
CACHE PATH "Single directory for all executable and dynamic libraries on Windows.")
|
||||
MARK_AS_ADVANCED( CMAKE_RUNTIME_OUTPUT_DIRECTORY CMAKE_LIBRARY_OUTPUT_DIRECTORY CMAKE_ARCHIVE_OUTPUT_DIRECTORY )
|
||||
|
||||
# Set variable named ${VAR_NAME} to value ${VALUE}
|
||||
FUNCTION(set_using_dynamic_name VAR_NAME VALUE)
|
||||
SET( "${VAR_NAME}" "${VALUE}" PARENT_SCOPE)
|
||||
@@ -83,14 +91,6 @@ if ( MSVC )
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /W4 ")
|
||||
endif( MSVC )
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
# using regular Clang or AppleClang
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
||||
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
||||
# using GCC
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
||||
endif()
|
||||
|
||||
IF(JSONCPP_WITH_WARNING_AS_ERROR)
|
||||
UseCompilationWarningAsError()
|
||||
ENDIF(JSONCPP_WITH_WARNING_AS_ERROR)
|
||||
@@ -101,7 +101,7 @@ IF(JSONCPP_WITH_PKGCONFIG_SUPPORT)
|
||||
"pkg-config/jsoncpp.pc"
|
||||
@ONLY)
|
||||
INSTALL(FILES "${CMAKE_BINARY_DIR}/pkg-config/jsoncpp.pc"
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}/pkgconfig")
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig")
|
||||
ENDIF(JSONCPP_WITH_PKGCONFIG_SUPPORT)
|
||||
|
||||
IF(JSONCPP_WITH_CMAKE_PACKAGE)
|
||||
|
2
NEWS.txt
2
NEWS.txt
@@ -80,7 +80,7 @@ New in SVN
|
||||
(e.g. MSVC 2008 command prompt in start menu) before running scons.
|
||||
|
||||
- Added support for amalgamated source and header generation (a la sqlite).
|
||||
Refer to README.md section "Generating amalgamated source and header"
|
||||
Refer to README.txt section "Generating amalgamated source and header"
|
||||
for detail.
|
||||
|
||||
* Value
|
||||
|
44
README.md
44
README.md
@@ -7,20 +7,17 @@ pairs.
|
||||
|
||||
[json-org]: http://json.org/
|
||||
|
||||
[JsonCpp][] is a C++ library that allows manipulating JSON values, including
|
||||
JsonCpp is a C++ library that allows manipulating JSON values, including
|
||||
serialization and deserialization to and from strings. It can also preserve
|
||||
existing comment in unserialization/serialization steps, making it a convenient
|
||||
format to store user input files.
|
||||
|
||||
[JsonCpp]: http://open-source-parsers.github.io/jsoncpp-docs/doxygen/index.html
|
||||
|
||||
## A note on backward-compatibility
|
||||
* `1.y.z` is built with C++11.
|
||||
* `0.8.z` can be used with older compilers.
|
||||
* Major versions maintain binary-compatibility.
|
||||
Very soon, we are switching to C++11 only. For older compilers, try the `pre-C++11` branch.
|
||||
|
||||
Using JsonCpp in your project
|
||||
-----------------------------
|
||||
|
||||
The recommended approach to integrating JsonCpp in your project is to build
|
||||
the amalgamated source (a single `.cpp` file) with your own build system. This
|
||||
ensures consistency of compilation flags and ABI compatibility. See the section
|
||||
@@ -31,11 +28,13 @@ should be included as follow:
|
||||
|
||||
#include <json/json.h>
|
||||
|
||||
If JsonCpp was built as a dynamic library on Windows, then your project needs to
|
||||
If JsonCpp was build as a dynamic library on Windows, then your project needs to
|
||||
define the macro `JSON_DLL`.
|
||||
|
||||
Building and testing with CMake
|
||||
-------------------------------
|
||||
|
||||
Building and testing with new CMake
|
||||
-----------------------------------
|
||||
|
||||
[CMake][] is a C++ Makefiles/Solution generator. It is usually available on most
|
||||
Linux system as package. On Ubuntu:
|
||||
|
||||
@@ -67,7 +66,7 @@ Alternatively, from the command-line on Unix in the source directory:
|
||||
|
||||
mkdir -p build/debug
|
||||
cd build/debug
|
||||
cmake -DCMAKE_BUILD_TYPE=debug -DJSONCPP_LIB_BUILD_STATIC=ON -DJSONCPP_LIB_BUILD_SHARED=OFF -G "Unix Makefiles" ../..
|
||||
cmake -DCMAKE_BUILD_TYPE=debug -DJSONCPP_LIB_BUILD_SHARED=OFF -G "Unix Makefiles" ../..
|
||||
make
|
||||
|
||||
Running `cmake -`" will display the list of available generators (passed using
|
||||
@@ -76,8 +75,10 @@ the `-G` option).
|
||||
By default CMake hides compilation commands. This can be modified by specifying
|
||||
`-DCMAKE_VERBOSE_MAKEFILE=true` when generating makefiles.
|
||||
|
||||
|
||||
Building and testing with SCons
|
||||
-------------------------------
|
||||
|
||||
**Note:** The SCons-based build system is deprecated. Please use CMake; see the
|
||||
section above.
|
||||
|
||||
@@ -106,7 +107,14 @@ If you are building with Microsoft Visual Studio 2008, you need to set up the
|
||||
environment by running `vcvars32.bat` (e.g. MSVC 2008 command prompt) before
|
||||
running SCons.
|
||||
|
||||
# Running the tests manually
|
||||
|
||||
Running the tests manually
|
||||
--------------------------
|
||||
|
||||
Note that test can be run using SCons using the `check` target:
|
||||
|
||||
scons platform=$PLATFORM check
|
||||
|
||||
You need to run tests manually only if you are troubleshooting an issue.
|
||||
|
||||
In the instructions below, replace `path/to/jsontest` with the path of the
|
||||
@@ -129,21 +137,20 @@ In the instructions below, replace `path/to/jsontest` with the path of the
|
||||
# You can run the tests using valgrind:
|
||||
python rununittests.py --valgrind path/to/test_lib_json
|
||||
|
||||
## Running the tests using scons
|
||||
Note that tests can be run using SCons using the `check` target:
|
||||
|
||||
scons platform=$PLATFORM check
|
||||
|
||||
Building the documentation
|
||||
--------------------------
|
||||
|
||||
Run the Python script `doxybuild.py` from the top directory:
|
||||
|
||||
python doxybuild.py --doxygen=$(which doxygen) --open --with-dot
|
||||
|
||||
See `doxybuild.py --help` for options.
|
||||
|
||||
|
||||
Generating amalgamated source and header
|
||||
----------------------------------------
|
||||
|
||||
JsonCpp is provided with a script to generate a single header and a single
|
||||
source file to ease inclusion into an existing project. The amalgamated source
|
||||
can be generated at any time by running the following command from the
|
||||
@@ -165,8 +172,10 @@ The amalgamated sources are generated by concatenating JsonCpp source in the
|
||||
correct order and defining the macro `JSON_IS_AMALGAMATION` to prevent inclusion
|
||||
of other headers.
|
||||
|
||||
|
||||
Adding a reader/writer test
|
||||
---------------------------
|
||||
|
||||
To add a test, you need to create two files in test/data:
|
||||
|
||||
* a `TESTNAME.json` file, that contains the input document in JSON format.
|
||||
@@ -186,8 +195,10 @@ The `TESTNAME.expected` file format is as follows:
|
||||
See the examples `test_complex_01.json` and `test_complex_01.expected` to better
|
||||
understand element paths.
|
||||
|
||||
|
||||
Understanding reader/writer test output
|
||||
---------------------------------------
|
||||
|
||||
When a test is run, output files are generated beside the input test files.
|
||||
Below is a short description of the content of each file:
|
||||
|
||||
@@ -204,7 +215,10 @@ Below is a short description of the content of each file:
|
||||
* `test_complex_01.process-output`: `jsontest` output, typically useful for
|
||||
understanding parsing errors.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
See the `LICENSE` file for details. In summary, JsonCpp is licensed under the
|
||||
MIT license, or public domain if desired and recognized in your jurisdiction.
|
||||
|
||||
|
@@ -237,7 +237,7 @@ RunUnitTests = ActionFactory(runUnitTests_action, runUnitTests_string )
|
||||
env.Alias( 'check' )
|
||||
|
||||
srcdist_cmd = env['SRCDIST_ADD']( source = """
|
||||
AUTHORS README.md SConstruct
|
||||
AUTHORS README.txt SConstruct
|
||||
""".split() )
|
||||
env.Alias( 'src-dist', srcdist_cmd )
|
||||
|
||||
|
181
amalgamate.py
181
amalgamate.py
@@ -10,121 +10,116 @@ import os.path
|
||||
import sys
|
||||
|
||||
class AmalgamationFile:
|
||||
def __init__(self, top_dir):
|
||||
def __init__( self, top_dir ):
|
||||
self.top_dir = top_dir
|
||||
self.blocks = []
|
||||
|
||||
def add_text(self, text):
|
||||
if not text.endswith("\n"):
|
||||
def add_text( self, text ):
|
||||
if not text.endswith( "\n" ):
|
||||
text += "\n"
|
||||
self.blocks.append(text)
|
||||
self.blocks.append( text )
|
||||
|
||||
def add_file(self, relative_input_path, wrap_in_comment=False):
|
||||
def add_marker(prefix):
|
||||
self.add_text("")
|
||||
self.add_text("// " + "/"*70)
|
||||
self.add_text("// %s of content of file: %s" % (prefix, relative_input_path.replace("\\","/")))
|
||||
self.add_text("// " + "/"*70)
|
||||
self.add_text("")
|
||||
add_marker("Beginning")
|
||||
f = open(os.path.join(self.top_dir, relative_input_path), "rt")
|
||||
def add_file( self, relative_input_path, wrap_in_comment=False ):
|
||||
def add_marker( prefix ):
|
||||
self.add_text( "" )
|
||||
self.add_text( "// " + "/"*70 )
|
||||
self.add_text( "// %s of content of file: %s" % (prefix, relative_input_path.replace("\\","/")) )
|
||||
self.add_text( "// " + "/"*70 )
|
||||
self.add_text( "" )
|
||||
add_marker( "Beginning" )
|
||||
f = open( os.path.join( self.top_dir, relative_input_path ), "rt" )
|
||||
content = f.read()
|
||||
if wrap_in_comment:
|
||||
content = "/*\n" + content + "\n*/"
|
||||
self.add_text(content)
|
||||
self.add_text( content )
|
||||
f.close()
|
||||
add_marker("End")
|
||||
self.add_text("\n\n\n\n")
|
||||
add_marker( "End" )
|
||||
self.add_text( "\n\n\n\n" )
|
||||
|
||||
def get_value(self):
|
||||
return "".join(self.blocks).replace("\r\n","\n")
|
||||
def get_value( self ):
|
||||
return "".join( self.blocks ).replace("\r\n","\n")
|
||||
|
||||
def write_to(self, output_path):
|
||||
output_dir = os.path.dirname(output_path)
|
||||
if output_dir and not os.path.isdir(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
f = open(output_path, "wb")
|
||||
f.write(str.encode(self.get_value(), 'UTF-8'))
|
||||
def write_to( self, output_path ):
|
||||
output_dir = os.path.dirname( output_path )
|
||||
if output_dir and not os.path.isdir( output_dir ):
|
||||
os.makedirs( output_dir )
|
||||
f = open( output_path, "wb" )
|
||||
f.write( str.encode(self.get_value(), 'UTF-8') )
|
||||
f.close()
|
||||
|
||||
def amalgamate_source(source_top_dir=None,
|
||||
def amalgamate_source( source_top_dir=None,
|
||||
target_source_path=None,
|
||||
header_include_path=None):
|
||||
header_include_path=None ):
|
||||
"""Produces amalgated source.
|
||||
Parameters:
|
||||
source_top_dir: top-directory
|
||||
target_source_path: output .cpp path
|
||||
header_include_path: generated header path relative to target_source_path.
|
||||
"""
|
||||
print("Amalgating header...")
|
||||
header = AmalgamationFile(source_top_dir)
|
||||
header.add_text("/// Json-cpp amalgated header (http://jsoncpp.sourceforge.net/).")
|
||||
header.add_text('/// It is intended to be used with #include "%s"' % header_include_path)
|
||||
header.add_file("LICENSE", wrap_in_comment=True)
|
||||
header.add_text("#ifndef JSON_AMALGATED_H_INCLUDED")
|
||||
header.add_text("# define JSON_AMALGATED_H_INCLUDED")
|
||||
header.add_text("/// If defined, indicates that the source file is amalgated")
|
||||
header.add_text("/// to prevent private header inclusion.")
|
||||
header.add_text("#define JSON_IS_AMALGAMATION")
|
||||
header.add_file("include/json/version.h")
|
||||
header.add_file("include/json/config.h")
|
||||
header.add_file("include/json/forwards.h")
|
||||
header.add_file("include/json/features.h")
|
||||
header.add_file("include/json/value.h")
|
||||
header.add_file("include/json/reader.h")
|
||||
header.add_file("include/json/writer.h")
|
||||
header.add_file("include/json/assertions.h")
|
||||
header.add_text("#endif //ifndef JSON_AMALGATED_H_INCLUDED")
|
||||
print ("Amalgating header...")
|
||||
header = AmalgamationFile( source_top_dir )
|
||||
header.add_text( "/// Json-cpp amalgated header (http://jsoncpp.sourceforge.net/)." )
|
||||
header.add_text( "/// It is intented to be used with #include <%s>" % header_include_path )
|
||||
header.add_file( "LICENSE", wrap_in_comment=True )
|
||||
header.add_text( "#ifndef JSON_AMALGATED_H_INCLUDED" )
|
||||
header.add_text( "# define JSON_AMALGATED_H_INCLUDED" )
|
||||
header.add_text( "/// If defined, indicates that the source file is amalgated" )
|
||||
header.add_text( "/// to prevent private header inclusion." )
|
||||
header.add_text( "#define JSON_IS_AMALGAMATION" )
|
||||
header.add_file( "include/json/version.h" )
|
||||
header.add_file( "include/json/config.h" )
|
||||
header.add_file( "include/json/forwards.h" )
|
||||
header.add_file( "include/json/features.h" )
|
||||
header.add_file( "include/json/value.h" )
|
||||
header.add_file( "include/json/reader.h" )
|
||||
header.add_file( "include/json/writer.h" )
|
||||
header.add_file( "include/json/assertions.h" )
|
||||
header.add_text( "#endif //ifndef JSON_AMALGATED_H_INCLUDED" )
|
||||
|
||||
target_header_path = os.path.join(os.path.dirname(target_source_path), header_include_path)
|
||||
print("Writing amalgated header to %r" % target_header_path)
|
||||
header.write_to(target_header_path)
|
||||
target_header_path = os.path.join( os.path.dirname(target_source_path), header_include_path )
|
||||
print ("Writing amalgated header to %r" % target_header_path)
|
||||
header.write_to( target_header_path )
|
||||
|
||||
base, ext = os.path.splitext(header_include_path)
|
||||
base, ext = os.path.splitext( header_include_path )
|
||||
forward_header_include_path = base + "-forwards" + ext
|
||||
print("Amalgating forward header...")
|
||||
header = AmalgamationFile(source_top_dir)
|
||||
header.add_text("/// Json-cpp amalgated forward header (http://jsoncpp.sourceforge.net/).")
|
||||
header.add_text('/// It is intended to be used with #include "%s"' % forward_header_include_path)
|
||||
header.add_text("/// This header provides forward declaration for all JsonCpp types.")
|
||||
header.add_file("LICENSE", wrap_in_comment=True)
|
||||
header.add_text("#ifndef JSON_FORWARD_AMALGATED_H_INCLUDED")
|
||||
header.add_text("# define JSON_FORWARD_AMALGATED_H_INCLUDED")
|
||||
header.add_text("/// If defined, indicates that the source file is amalgated")
|
||||
header.add_text("/// to prevent private header inclusion.")
|
||||
header.add_text("#define JSON_IS_AMALGAMATION")
|
||||
header.add_file("include/json/config.h")
|
||||
header.add_file("include/json/forwards.h")
|
||||
header.add_text("#endif //ifndef JSON_FORWARD_AMALGATED_H_INCLUDED")
|
||||
print ("Amalgating forward header...")
|
||||
header = AmalgamationFile( source_top_dir )
|
||||
header.add_text( "/// Json-cpp amalgated forward header (http://jsoncpp.sourceforge.net/)." )
|
||||
header.add_text( "/// It is intented to be used with #include <%s>" % forward_header_include_path )
|
||||
header.add_text( "/// This header provides forward declaration for all JsonCpp types." )
|
||||
header.add_file( "LICENSE", wrap_in_comment=True )
|
||||
header.add_text( "#ifndef JSON_FORWARD_AMALGATED_H_INCLUDED" )
|
||||
header.add_text( "# define JSON_FORWARD_AMALGATED_H_INCLUDED" )
|
||||
header.add_text( "/// If defined, indicates that the source file is amalgated" )
|
||||
header.add_text( "/// to prevent private header inclusion." )
|
||||
header.add_text( "#define JSON_IS_AMALGAMATION" )
|
||||
header.add_file( "include/json/config.h" )
|
||||
header.add_file( "include/json/forwards.h" )
|
||||
header.add_text( "#endif //ifndef JSON_FORWARD_AMALGATED_H_INCLUDED" )
|
||||
|
||||
target_forward_header_path = os.path.join(os.path.dirname(target_source_path),
|
||||
forward_header_include_path)
|
||||
print("Writing amalgated forward header to %r" % target_forward_header_path)
|
||||
header.write_to(target_forward_header_path)
|
||||
target_forward_header_path = os.path.join( os.path.dirname(target_source_path),
|
||||
forward_header_include_path )
|
||||
print ("Writing amalgated forward header to %r" % target_forward_header_path)
|
||||
header.write_to( target_forward_header_path )
|
||||
|
||||
print("Amalgating source...")
|
||||
source = AmalgamationFile(source_top_dir)
|
||||
source.add_text("/// Json-cpp amalgated source (http://jsoncpp.sourceforge.net/).")
|
||||
source.add_text('/// It is intended to be used with #include "%s"' % header_include_path)
|
||||
source.add_file("LICENSE", wrap_in_comment=True)
|
||||
source.add_text("")
|
||||
source.add_text('#include "%s"' % header_include_path)
|
||||
source.add_text("""
|
||||
#ifndef JSON_IS_AMALGAMATION
|
||||
#error "Compile with -I PATH_TO_JSON_DIRECTORY"
|
||||
#endif
|
||||
""")
|
||||
source.add_text("")
|
||||
print ("Amalgating source...")
|
||||
source = AmalgamationFile( source_top_dir )
|
||||
source.add_text( "/// Json-cpp amalgated source (http://jsoncpp.sourceforge.net/)." )
|
||||
source.add_text( "/// It is intented to be used with #include <%s>" % header_include_path )
|
||||
source.add_file( "LICENSE", wrap_in_comment=True )
|
||||
source.add_text( "" )
|
||||
source.add_text( "#include <%s>" % header_include_path )
|
||||
source.add_text( "" )
|
||||
lib_json = "src/lib_json"
|
||||
source.add_file(os.path.join(lib_json, "json_tool.h"))
|
||||
source.add_file(os.path.join(lib_json, "json_reader.cpp"))
|
||||
source.add_file(os.path.join(lib_json, "json_batchallocator.h"))
|
||||
source.add_file(os.path.join(lib_json, "json_valueiterator.inl"))
|
||||
source.add_file(os.path.join(lib_json, "json_value.cpp"))
|
||||
source.add_file(os.path.join(lib_json, "json_writer.cpp"))
|
||||
source.add_file( os.path.join(lib_json, "json_tool.h") )
|
||||
source.add_file( os.path.join(lib_json, "json_reader.cpp") )
|
||||
source.add_file( os.path.join(lib_json, "json_batchallocator.h") )
|
||||
source.add_file( os.path.join(lib_json, "json_valueiterator.inl") )
|
||||
source.add_file( os.path.join(lib_json, "json_value.cpp") )
|
||||
source.add_file( os.path.join(lib_json, "json_writer.cpp") )
|
||||
|
||||
print("Writing amalgated source to %r" % target_source_path)
|
||||
source.write_to(target_source_path)
|
||||
print ("Writing amalgated source to %r" % target_source_path)
|
||||
source.write_to( target_source_path )
|
||||
|
||||
def main():
|
||||
usage = """%prog [options]
|
||||
@@ -142,14 +137,14 @@ Generate a single amalgated source and header file from the sources.
|
||||
parser.enable_interspersed_args()
|
||||
options, args = parser.parse_args()
|
||||
|
||||
msg = amalgamate_source(source_top_dir=options.top_dir,
|
||||
msg = amalgamate_source( source_top_dir=options.top_dir,
|
||||
target_source_path=options.target_source_path,
|
||||
header_include_path=options.header_include_path)
|
||||
header_include_path=options.header_include_path )
|
||||
if msg:
|
||||
sys.stderr.write(msg + "\n")
|
||||
sys.exit(1)
|
||||
sys.stderr.write( msg + "\n" )
|
||||
sys.exit( 1 )
|
||||
else:
|
||||
print("Source succesfully amalagated")
|
||||
print ("Source succesfully amalagated")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
21
dev.makefile
21
dev.makefile
@@ -1,19 +1,5 @@
|
||||
# This is only for jsoncpp developers/contributors.
|
||||
# We use this to sign releases, generate documentation, etc.
|
||||
VER?=$(shell cat version)
|
||||
all: build test-amalgamate
|
||||
|
||||
default:
|
||||
@echo "VER=${VER}"
|
||||
sign: jsoncpp-${VER}.tar.gz
|
||||
gpg --armor --detach-sign $<
|
||||
gpg --verify $<.asc
|
||||
# Then upload .asc to the release.
|
||||
jsoncpp-%.tar.gz:
|
||||
curl https://github.com/open-source-parsers/jsoncpp/archive/$*.tar.gz -o $@
|
||||
dox:
|
||||
python doxybuild.py --doxygen=$$(which doxygen) --in doc/web_doxyfile.in
|
||||
rsync -va --delete dist/doxygen/jsoncpp-api-html-${VER}/ ../jsoncpp-docs/doxygen/
|
||||
# Then 'git add -A' and 'git push' in jsoncpp-docs.
|
||||
build:
|
||||
mkdir -p build/debug
|
||||
cd build/debug; cmake -DCMAKE_BUILD_TYPE=debug -DJSONCPP_LIB_BUILD_SHARED=ON -G "Unix Makefiles" ../..
|
||||
@@ -21,11 +7,8 @@ build:
|
||||
|
||||
# Currently, this depends on include/json/version.h generated
|
||||
# by cmake.
|
||||
test-amalgamate:
|
||||
test-amalgamate: build
|
||||
python2.7 amalgamate.py
|
||||
python3.4 amalgamate.py
|
||||
|
||||
clean:
|
||||
\rm -rf *.gz *.asc dist/
|
||||
|
||||
.PHONY: build
|
||||
|
@@ -2,7 +2,6 @@
|
||||
# encoding: utf-8
|
||||
# Baptiste Lepilleur, 2009
|
||||
|
||||
from __future__ import print_function
|
||||
from dircache import listdir
|
||||
import re
|
||||
import fnmatch
|
||||
@@ -54,9 +53,9 @@ LINKS = DIR_LINK | FILE_LINK
|
||||
ALL_NO_LINK = DIR | FILE
|
||||
ALL = DIR | FILE | LINKS
|
||||
|
||||
_ANT_RE = re.compile(r'(/\*\*/)|(\*\*/)|(/\*\*)|(\*)|(/)|([^\*/]*)')
|
||||
_ANT_RE = re.compile( r'(/\*\*/)|(\*\*/)|(/\*\*)|(\*)|(/)|([^\*/]*)' )
|
||||
|
||||
def ant_pattern_to_re(ant_pattern):
|
||||
def ant_pattern_to_re( ant_pattern ):
|
||||
"""Generates a regular expression from the ant pattern.
|
||||
Matching convention:
|
||||
**/a: match 'a', 'dir/a', 'dir1/dir2/a'
|
||||
@@ -65,30 +64,30 @@ def ant_pattern_to_re(ant_pattern):
|
||||
"""
|
||||
rex = ['^']
|
||||
next_pos = 0
|
||||
sep_rex = r'(?:/|%s)' % re.escape(os.path.sep)
|
||||
sep_rex = r'(?:/|%s)' % re.escape( os.path.sep )
|
||||
## print 'Converting', ant_pattern
|
||||
for match in _ANT_RE.finditer(ant_pattern):
|
||||
for match in _ANT_RE.finditer( ant_pattern ):
|
||||
## print 'Matched', match.group()
|
||||
## print match.start(0), next_pos
|
||||
if match.start(0) != next_pos:
|
||||
raise ValueError("Invalid ant pattern")
|
||||
raise ValueError( "Invalid ant pattern" )
|
||||
if match.group(1): # /**/
|
||||
rex.append(sep_rex + '(?:.*%s)?' % sep_rex)
|
||||
rex.append( sep_rex + '(?:.*%s)?' % sep_rex )
|
||||
elif match.group(2): # **/
|
||||
rex.append('(?:.*%s)?' % sep_rex)
|
||||
rex.append( '(?:.*%s)?' % sep_rex )
|
||||
elif match.group(3): # /**
|
||||
rex.append(sep_rex + '.*')
|
||||
rex.append( sep_rex + '.*' )
|
||||
elif match.group(4): # *
|
||||
rex.append('[^/%s]*' % re.escape(os.path.sep))
|
||||
rex.append( '[^/%s]*' % re.escape(os.path.sep) )
|
||||
elif match.group(5): # /
|
||||
rex.append(sep_rex)
|
||||
rex.append( sep_rex )
|
||||
else: # somepath
|
||||
rex.append(re.escape(match.group(6)))
|
||||
rex.append( re.escape(match.group(6)) )
|
||||
next_pos = match.end()
|
||||
rex.append('$')
|
||||
return re.compile(''.join(rex))
|
||||
return re.compile( ''.join( rex ) )
|
||||
|
||||
def _as_list(l):
|
||||
def _as_list( l ):
|
||||
if isinstance(l, basestring):
|
||||
return l.split()
|
||||
return l
|
||||
@@ -105,37 +104,37 @@ def glob(dir_path,
|
||||
dir_path = dir_path.replace('/',os.path.sep)
|
||||
entry_type_filter = entry_type
|
||||
|
||||
def is_pruned_dir(dir_name):
|
||||
def is_pruned_dir( dir_name ):
|
||||
for pattern in prune_dirs:
|
||||
if fnmatch.fnmatch(dir_name, pattern):
|
||||
if fnmatch.fnmatch( dir_name, pattern ):
|
||||
return True
|
||||
return False
|
||||
|
||||
def apply_filter(full_path, filter_rexs):
|
||||
def apply_filter( full_path, filter_rexs ):
|
||||
"""Return True if at least one of the filter regular expression match full_path."""
|
||||
for rex in filter_rexs:
|
||||
if rex.match(full_path):
|
||||
if rex.match( full_path ):
|
||||
return True
|
||||
return False
|
||||
|
||||
def glob_impl(root_dir_path):
|
||||
def glob_impl( root_dir_path ):
|
||||
child_dirs = [root_dir_path]
|
||||
while child_dirs:
|
||||
dir_path = child_dirs.pop()
|
||||
for entry in listdir(dir_path):
|
||||
full_path = os.path.join(dir_path, entry)
|
||||
for entry in listdir( dir_path ):
|
||||
full_path = os.path.join( dir_path, entry )
|
||||
## print 'Testing:', full_path,
|
||||
is_dir = os.path.isdir(full_path)
|
||||
if is_dir and not is_pruned_dir(entry): # explore child directory ?
|
||||
is_dir = os.path.isdir( full_path )
|
||||
if is_dir and not is_pruned_dir( entry ): # explore child directory ?
|
||||
## print '===> marked for recursion',
|
||||
child_dirs.append(full_path)
|
||||
included = apply_filter(full_path, include_filter)
|
||||
rejected = apply_filter(full_path, exclude_filter)
|
||||
child_dirs.append( full_path )
|
||||
included = apply_filter( full_path, include_filter )
|
||||
rejected = apply_filter( full_path, exclude_filter )
|
||||
if not included or rejected: # do not include entry ?
|
||||
## print '=> not included or rejected'
|
||||
continue
|
||||
link = os.path.islink(full_path)
|
||||
is_file = os.path.isfile(full_path)
|
||||
link = os.path.islink( full_path )
|
||||
is_file = os.path.isfile( full_path )
|
||||
if not is_file and not is_dir:
|
||||
## print '=> unknown entry type'
|
||||
continue
|
||||
@@ -146,57 +145,57 @@ def glob(dir_path,
|
||||
## print '=> type: %d' % entry_type,
|
||||
if (entry_type & entry_type_filter) != 0:
|
||||
## print ' => KEEP'
|
||||
yield os.path.join(dir_path, entry)
|
||||
yield os.path.join( dir_path, entry )
|
||||
## else:
|
||||
## print ' => TYPE REJECTED'
|
||||
return list(glob_impl(dir_path))
|
||||
return list( glob_impl( dir_path ) )
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import unittest
|
||||
|
||||
class AntPatternToRETest(unittest.TestCase):
|
||||
## def test_conversion(self):
|
||||
## self.assertEqual('^somepath$', ant_pattern_to_re('somepath').pattern)
|
||||
## def test_conversion( self ):
|
||||
## self.assertEqual( '^somepath$', ant_pattern_to_re( 'somepath' ).pattern )
|
||||
|
||||
def test_matching(self):
|
||||
test_cases = [ ('path',
|
||||
def test_matching( self ):
|
||||
test_cases = [ ( 'path',
|
||||
['path'],
|
||||
['somepath', 'pathsuffix', '/path', '/path']),
|
||||
('*.py',
|
||||
['somepath', 'pathsuffix', '/path', '/path'] ),
|
||||
( '*.py',
|
||||
['source.py', 'source.ext.py', '.py'],
|
||||
['path/source.py', '/.py', 'dir.py/z', 'z.pyc', 'z.c']),
|
||||
('**/path',
|
||||
['path/source.py', '/.py', 'dir.py/z', 'z.pyc', 'z.c'] ),
|
||||
( '**/path',
|
||||
['path', '/path', '/a/path', 'c:/a/path', '/a/b/path', '//a/path', '/a/path/b/path'],
|
||||
['path/', 'a/path/b', 'dir.py/z', 'somepath', 'pathsuffix', 'a/somepath']),
|
||||
('path/**',
|
||||
['path/', 'a/path/b', 'dir.py/z', 'somepath', 'pathsuffix', 'a/somepath'] ),
|
||||
( 'path/**',
|
||||
['path/a', 'path/path/a', 'path//'],
|
||||
['path', 'somepath/a', 'a/path', 'a/path/a', 'pathsuffix/a']),
|
||||
('/**/path',
|
||||
['path', 'somepath/a', 'a/path', 'a/path/a', 'pathsuffix/a'] ),
|
||||
( '/**/path',
|
||||
['/path', '/a/path', '/a/b/path/path', '/path/path'],
|
||||
['path', 'path/', 'a/path', '/pathsuffix', '/somepath']),
|
||||
('a/b',
|
||||
['path', 'path/', 'a/path', '/pathsuffix', '/somepath'] ),
|
||||
( 'a/b',
|
||||
['a/b'],
|
||||
['somea/b', 'a/bsuffix', 'a/b/c']),
|
||||
('**/*.py',
|
||||
['somea/b', 'a/bsuffix', 'a/b/c'] ),
|
||||
( '**/*.py',
|
||||
['script.py', 'src/script.py', 'a/b/script.py', '/a/b/script.py'],
|
||||
['script.pyc', 'script.pyo', 'a.py/b']),
|
||||
('src/**/*.py',
|
||||
['script.pyc', 'script.pyo', 'a.py/b'] ),
|
||||
( 'src/**/*.py',
|
||||
['src/a.py', 'src/dir/a.py'],
|
||||
['a/src/a.py', '/src/a.py']),
|
||||
['a/src/a.py', '/src/a.py'] ),
|
||||
]
|
||||
for ant_pattern, accepted_matches, rejected_matches in list(test_cases):
|
||||
def local_path(paths):
|
||||
def local_path( paths ):
|
||||
return [ p.replace('/',os.path.sep) for p in paths ]
|
||||
test_cases.append((ant_pattern, local_path(accepted_matches), local_path(rejected_matches)))
|
||||
test_cases.append( (ant_pattern, local_path(accepted_matches), local_path( rejected_matches )) )
|
||||
for ant_pattern, accepted_matches, rejected_matches in test_cases:
|
||||
rex = ant_pattern_to_re(ant_pattern)
|
||||
print('ant_pattern:', ant_pattern, ' => ', rex.pattern)
|
||||
rex = ant_pattern_to_re( ant_pattern )
|
||||
print 'ant_pattern:', ant_pattern, ' => ', rex.pattern
|
||||
for accepted_match in accepted_matches:
|
||||
print('Accepted?:', accepted_match)
|
||||
self.assertTrue(rex.match(accepted_match) is not None)
|
||||
print 'Accepted?:', accepted_match
|
||||
self.assert_( rex.match( accepted_match ) is not None )
|
||||
for rejected_match in rejected_matches:
|
||||
print('Rejected?:', rejected_match)
|
||||
self.assertTrue(rex.match(rejected_match) is None)
|
||||
print 'Rejected?:', rejected_match
|
||||
self.assert_( rex.match( rejected_match ) is None )
|
||||
|
||||
unittest.main()
|
||||
|
@@ -1,278 +1,280 @@
|
||||
from __future__ import print_function
|
||||
import collections
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import cgi
|
||||
|
||||
class BuildDesc:
|
||||
def __init__(self, prepend_envs=None, variables=None, build_type=None, generator=None):
|
||||
self.prepend_envs = prepend_envs or [] # [ { "var": "value" } ]
|
||||
self.variables = variables or []
|
||||
self.build_type = build_type
|
||||
self.generator = generator
|
||||
|
||||
def merged_with(self, build_desc):
|
||||
"""Returns a new BuildDesc by merging field content.
|
||||
Prefer build_desc fields to self fields for single valued field.
|
||||
"""
|
||||
return BuildDesc(self.prepend_envs + build_desc.prepend_envs,
|
||||
self.variables + build_desc.variables,
|
||||
build_desc.build_type or self.build_type,
|
||||
build_desc.generator or self.generator)
|
||||
|
||||
def env(self):
|
||||
environ = os.environ.copy()
|
||||
for values_by_name in self.prepend_envs:
|
||||
for var, value in list(values_by_name.items()):
|
||||
var = var.upper()
|
||||
if type(value) is unicode:
|
||||
value = value.encode(sys.getdefaultencoding())
|
||||
if var in environ:
|
||||
environ[var] = value + os.pathsep + environ[var]
|
||||
else:
|
||||
environ[var] = value
|
||||
return environ
|
||||
|
||||
def cmake_args(self):
|
||||
args = ["-D%s" % var for var in self.variables]
|
||||
# skip build type for Visual Studio solution as it cause warning
|
||||
if self.build_type and 'Visual' not in self.generator:
|
||||
args.append("-DCMAKE_BUILD_TYPE=%s" % self.build_type)
|
||||
if self.generator:
|
||||
args.extend(['-G', self.generator])
|
||||
return args
|
||||
|
||||
def __repr__(self):
|
||||
return "BuildDesc(%s, build_type=%s)" % (" ".join(self.cmake_args()), self.build_type)
|
||||
|
||||
class BuildData:
|
||||
def __init__(self, desc, work_dir, source_dir):
|
||||
self.desc = desc
|
||||
self.work_dir = work_dir
|
||||
self.source_dir = source_dir
|
||||
self.cmake_log_path = os.path.join(work_dir, 'batchbuild_cmake.log')
|
||||
self.build_log_path = os.path.join(work_dir, 'batchbuild_build.log')
|
||||
self.cmake_succeeded = False
|
||||
self.build_succeeded = False
|
||||
|
||||
def execute_build(self):
|
||||
print('Build %s' % self.desc)
|
||||
self._make_new_work_dir()
|
||||
self.cmake_succeeded = self._generate_makefiles()
|
||||
if self.cmake_succeeded:
|
||||
self.build_succeeded = self._build_using_makefiles()
|
||||
return self.build_succeeded
|
||||
|
||||
def _generate_makefiles(self):
|
||||
print(' Generating makefiles: ', end=' ')
|
||||
cmd = ['cmake'] + self.desc.cmake_args() + [os.path.abspath(self.source_dir)]
|
||||
succeeded = self._execute_build_subprocess(cmd, self.desc.env(), self.cmake_log_path)
|
||||
print('done' if succeeded else 'FAILED')
|
||||
return succeeded
|
||||
|
||||
def _build_using_makefiles(self):
|
||||
print(' Building:', end=' ')
|
||||
cmd = ['cmake', '--build', self.work_dir]
|
||||
if self.desc.build_type:
|
||||
cmd += ['--config', self.desc.build_type]
|
||||
succeeded = self._execute_build_subprocess(cmd, self.desc.env(), self.build_log_path)
|
||||
print('done' if succeeded else 'FAILED')
|
||||
return succeeded
|
||||
|
||||
def _execute_build_subprocess(self, cmd, env, log_path):
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=self.work_dir,
|
||||
env=env)
|
||||
stdout, _ = process.communicate()
|
||||
succeeded = (process.returncode == 0)
|
||||
with open(log_path, 'wb') as flog:
|
||||
log = ' '.join(cmd) + '\n' + stdout + '\nExit code: %r\n' % process.returncode
|
||||
flog.write(fix_eol(log))
|
||||
return succeeded
|
||||
|
||||
def _make_new_work_dir(self):
|
||||
if os.path.isdir(self.work_dir):
|
||||
print(' Removing work directory', self.work_dir)
|
||||
shutil.rmtree(self.work_dir, ignore_errors=True)
|
||||
if not os.path.isdir(self.work_dir):
|
||||
os.makedirs(self.work_dir)
|
||||
|
||||
def fix_eol(stdout):
|
||||
"""Fixes wrong EOL produced by cmake --build on Windows (\r\r\n instead of \r\n).
|
||||
"""
|
||||
return re.sub('\r*\n', os.linesep, stdout)
|
||||
|
||||
def load_build_variants_from_config(config_path):
|
||||
with open(config_path, 'rb') as fconfig:
|
||||
data = json.load(fconfig)
|
||||
variants = data[ 'cmake_variants' ]
|
||||
build_descs_by_axis = collections.defaultdict(list)
|
||||
for axis in variants:
|
||||
axis_name = axis["name"]
|
||||
build_descs = []
|
||||
if "generators" in axis:
|
||||
for generator_data in axis["generators"]:
|
||||
for generator in generator_data["generator"]:
|
||||
build_desc = BuildDesc(generator=generator,
|
||||
prepend_envs=generator_data.get("env_prepend"))
|
||||
build_descs.append(build_desc)
|
||||
elif "variables" in axis:
|
||||
for variables in axis["variables"]:
|
||||
build_desc = BuildDesc(variables=variables)
|
||||
build_descs.append(build_desc)
|
||||
elif "build_types" in axis:
|
||||
for build_type in axis["build_types"]:
|
||||
build_desc = BuildDesc(build_type=build_type)
|
||||
build_descs.append(build_desc)
|
||||
build_descs_by_axis[axis_name].extend(build_descs)
|
||||
return build_descs_by_axis
|
||||
|
||||
def generate_build_variants(build_descs_by_axis):
|
||||
"""Returns a list of BuildDesc generated for the partial BuildDesc for each axis."""
|
||||
axis_names = list(build_descs_by_axis.keys())
|
||||
build_descs = []
|
||||
for axis_name, axis_build_descs in list(build_descs_by_axis.items()):
|
||||
if len(build_descs):
|
||||
# for each existing build_desc and each axis build desc, create a new build_desc
|
||||
new_build_descs = []
|
||||
for prototype_build_desc, axis_build_desc in itertools.product(build_descs, axis_build_descs):
|
||||
new_build_descs.append(prototype_build_desc.merged_with(axis_build_desc))
|
||||
build_descs = new_build_descs
|
||||
else:
|
||||
build_descs = axis_build_descs
|
||||
return build_descs
|
||||
|
||||
HTML_TEMPLATE = string.Template('''<html>
|
||||
<head>
|
||||
<title>$title</title>
|
||||
<style type="text/css">
|
||||
td.failed {background-color:#f08080;}
|
||||
td.ok {background-color:#c0eec0;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variables</th>
|
||||
$th_vars
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Build type</th>
|
||||
$th_build_types
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
$tr_builds
|
||||
</tbody>
|
||||
</table>
|
||||
</body></html>''')
|
||||
|
||||
def generate_html_report(html_report_path, builds):
|
||||
report_dir = os.path.dirname(html_report_path)
|
||||
# Vertical axis: generator
|
||||
# Horizontal: variables, then build_type
|
||||
builds_by_generator = collections.defaultdict(list)
|
||||
variables = set()
|
||||
build_types_by_variable = collections.defaultdict(set)
|
||||
build_by_pos_key = {} # { (generator, var_key, build_type): build }
|
||||
for build in builds:
|
||||
builds_by_generator[build.desc.generator].append(build)
|
||||
var_key = tuple(sorted(build.desc.variables))
|
||||
variables.add(var_key)
|
||||
build_types_by_variable[var_key].add(build.desc.build_type)
|
||||
pos_key = (build.desc.generator, var_key, build.desc.build_type)
|
||||
build_by_pos_key[pos_key] = build
|
||||
variables = sorted(variables)
|
||||
th_vars = []
|
||||
th_build_types = []
|
||||
for variable in variables:
|
||||
build_types = sorted(build_types_by_variable[variable])
|
||||
nb_build_type = len(build_types_by_variable[variable])
|
||||
th_vars.append('<th colspan="%d">%s</th>' % (nb_build_type, cgi.escape(' '.join(variable))))
|
||||
for build_type in build_types:
|
||||
th_build_types.append('<th>%s</th>' % cgi.escape(build_type))
|
||||
tr_builds = []
|
||||
for generator in sorted(builds_by_generator):
|
||||
tds = [ '<td>%s</td>\n' % cgi.escape(generator) ]
|
||||
for variable in variables:
|
||||
build_types = sorted(build_types_by_variable[variable])
|
||||
for build_type in build_types:
|
||||
pos_key = (generator, variable, build_type)
|
||||
build = build_by_pos_key.get(pos_key)
|
||||
if build:
|
||||
cmake_status = 'ok' if build.cmake_succeeded else 'FAILED'
|
||||
build_status = 'ok' if build.build_succeeded else 'FAILED'
|
||||
cmake_log_url = os.path.relpath(build.cmake_log_path, report_dir)
|
||||
build_log_url = os.path.relpath(build.build_log_path, report_dir)
|
||||
td = '<td class="%s"><a href="%s" class="%s">CMake: %s</a>' % ( build_status.lower(), cmake_log_url, cmake_status.lower(), cmake_status)
|
||||
if build.cmake_succeeded:
|
||||
td += '<br><a href="%s" class="%s">Build: %s</a>' % ( build_log_url, build_status.lower(), build_status)
|
||||
td += '</td>'
|
||||
else:
|
||||
td = '<td></td>'
|
||||
tds.append(td)
|
||||
tr_builds.append('<tr>%s</tr>' % '\n'.join(tds))
|
||||
html = HTML_TEMPLATE.substitute( title='Batch build report',
|
||||
th_vars=' '.join(th_vars),
|
||||
th_build_types=' '.join(th_build_types),
|
||||
tr_builds='\n'.join(tr_builds))
|
||||
with open(html_report_path, 'wt') as fhtml:
|
||||
fhtml.write(html)
|
||||
print('HTML report generated in:', html_report_path)
|
||||
|
||||
def main():
|
||||
usage = r"""%prog WORK_DIR SOURCE_DIR CONFIG_JSON_PATH [CONFIG2_JSON_PATH...]
|
||||
Build a given CMake based project located in SOURCE_DIR with multiple generators/options.dry_run
|
||||
as described in CONFIG_JSON_PATH building in WORK_DIR.
|
||||
|
||||
Example of call:
|
||||
python devtools\batchbuild.py e:\buildbots\jsoncpp\build . devtools\agent_vmw7.json
|
||||
"""
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser(usage=usage)
|
||||
parser.allow_interspersed_args = True
|
||||
# parser.add_option('-v', '--verbose', dest="verbose", action='store_true',
|
||||
# help="""Be verbose.""")
|
||||
parser.enable_interspersed_args()
|
||||
options, args = parser.parse_args()
|
||||
if len(args) < 3:
|
||||
parser.error("Missing one of WORK_DIR SOURCE_DIR CONFIG_JSON_PATH.")
|
||||
work_dir = args[0]
|
||||
source_dir = args[1].rstrip('/\\')
|
||||
config_paths = args[2:]
|
||||
for config_path in config_paths:
|
||||
if not os.path.isfile(config_path):
|
||||
parser.error("Can not read: %r" % config_path)
|
||||
|
||||
# generate build variants
|
||||
build_descs = []
|
||||
for config_path in config_paths:
|
||||
build_descs_by_axis = load_build_variants_from_config(config_path)
|
||||
build_descs.extend(generate_build_variants(build_descs_by_axis))
|
||||
print('Build variants (%d):' % len(build_descs))
|
||||
# assign build directory for each variant
|
||||
if not os.path.isdir(work_dir):
|
||||
os.makedirs(work_dir)
|
||||
builds = []
|
||||
with open(os.path.join(work_dir, 'matrix-dir-map.txt'), 'wt') as fmatrixmap:
|
||||
for index, build_desc in enumerate(build_descs):
|
||||
build_desc_work_dir = os.path.join(work_dir, '%03d' % (index+1))
|
||||
builds.append(BuildData(build_desc, build_desc_work_dir, source_dir))
|
||||
fmatrixmap.write('%s: %s\n' % (build_desc_work_dir, build_desc))
|
||||
for build in builds:
|
||||
build.execute_build()
|
||||
html_report_path = os.path.join(work_dir, 'batchbuild-report.html')
|
||||
generate_html_report(html_report_path, builds)
|
||||
print('Done')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
import collections
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import cgi
|
||||
|
||||
class BuildDesc:
|
||||
def __init__(self, prepend_envs=None, variables=None, build_type=None, generator=None):
|
||||
self.prepend_envs = prepend_envs or [] # [ { "var": "value" } ]
|
||||
self.variables = variables or []
|
||||
self.build_type = build_type
|
||||
self.generator = generator
|
||||
|
||||
def merged_with( self, build_desc ):
|
||||
"""Returns a new BuildDesc by merging field content.
|
||||
Prefer build_desc fields to self fields for single valued field.
|
||||
"""
|
||||
return BuildDesc( self.prepend_envs + build_desc.prepend_envs,
|
||||
self.variables + build_desc.variables,
|
||||
build_desc.build_type or self.build_type,
|
||||
build_desc.generator or self.generator )
|
||||
|
||||
def env( self ):
|
||||
environ = os.environ.copy()
|
||||
for values_by_name in self.prepend_envs:
|
||||
for var, value in values_by_name.items():
|
||||
var = var.upper()
|
||||
if type(value) is unicode:
|
||||
value = value.encode( sys.getdefaultencoding() )
|
||||
if var in environ:
|
||||
environ[var] = value + os.pathsep + environ[var]
|
||||
else:
|
||||
environ[var] = value
|
||||
return environ
|
||||
|
||||
def cmake_args( self ):
|
||||
args = ["-D%s" % var for var in self.variables]
|
||||
# skip build type for Visual Studio solution as it cause warning
|
||||
if self.build_type and 'Visual' not in self.generator:
|
||||
args.append( "-DCMAKE_BUILD_TYPE=%s" % self.build_type )
|
||||
if self.generator:
|
||||
args.extend( ['-G', self.generator] )
|
||||
return args
|
||||
|
||||
def __repr__( self ):
|
||||
return "BuildDesc( %s, build_type=%s )" % (" ".join( self.cmake_args()), self.build_type)
|
||||
|
||||
class BuildData:
|
||||
def __init__( self, desc, work_dir, source_dir ):
|
||||
self.desc = desc
|
||||
self.work_dir = work_dir
|
||||
self.source_dir = source_dir
|
||||
self.cmake_log_path = os.path.join( work_dir, 'batchbuild_cmake.log' )
|
||||
self.build_log_path = os.path.join( work_dir, 'batchbuild_build.log' )
|
||||
self.cmake_succeeded = False
|
||||
self.build_succeeded = False
|
||||
|
||||
def execute_build(self):
|
||||
print 'Build %s' % self.desc
|
||||
self._make_new_work_dir( )
|
||||
self.cmake_succeeded = self._generate_makefiles( )
|
||||
if self.cmake_succeeded:
|
||||
self.build_succeeded = self._build_using_makefiles( )
|
||||
return self.build_succeeded
|
||||
|
||||
def _generate_makefiles(self):
|
||||
print ' Generating makefiles: ',
|
||||
cmd = ['cmake'] + self.desc.cmake_args( ) + [os.path.abspath( self.source_dir )]
|
||||
succeeded = self._execute_build_subprocess( cmd, self.desc.env(), self.cmake_log_path )
|
||||
print 'done' if succeeded else 'FAILED'
|
||||
return succeeded
|
||||
|
||||
def _build_using_makefiles(self):
|
||||
print ' Building:',
|
||||
cmd = ['cmake', '--build', self.work_dir]
|
||||
if self.desc.build_type:
|
||||
cmd += ['--config', self.desc.build_type]
|
||||
succeeded = self._execute_build_subprocess( cmd, self.desc.env(), self.build_log_path )
|
||||
print 'done' if succeeded else 'FAILED'
|
||||
return succeeded
|
||||
|
||||
def _execute_build_subprocess(self, cmd, env, log_path):
|
||||
process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=self.work_dir,
|
||||
env=env )
|
||||
stdout, _ = process.communicate( )
|
||||
succeeded = (process.returncode == 0)
|
||||
with open( log_path, 'wb' ) as flog:
|
||||
log = ' '.join( cmd ) + '\n' + stdout + '\nExit code: %r\n' % process.returncode
|
||||
flog.write( fix_eol( log ) )
|
||||
return succeeded
|
||||
|
||||
def _make_new_work_dir(self):
|
||||
if os.path.isdir( self.work_dir ):
|
||||
print ' Removing work directory', self.work_dir
|
||||
shutil.rmtree( self.work_dir, ignore_errors=True )
|
||||
if not os.path.isdir( self.work_dir ):
|
||||
os.makedirs( self.work_dir )
|
||||
|
||||
def fix_eol( stdout ):
|
||||
"""Fixes wrong EOL produced by cmake --build on Windows (\r\r\n instead of \r\n).
|
||||
"""
|
||||
return re.sub( '\r*\n', os.linesep, stdout )
|
||||
|
||||
def load_build_variants_from_config( config_path ):
|
||||
with open( config_path, 'rb' ) as fconfig:
|
||||
data = json.load( fconfig )
|
||||
variants = data[ 'cmake_variants' ]
|
||||
build_descs_by_axis = collections.defaultdict( list )
|
||||
for axis in variants:
|
||||
axis_name = axis["name"]
|
||||
build_descs = []
|
||||
if "generators" in axis:
|
||||
for generator_data in axis["generators"]:
|
||||
for generator in generator_data["generator"]:
|
||||
build_desc = BuildDesc( generator=generator,
|
||||
prepend_envs=generator_data.get("env_prepend") )
|
||||
build_descs.append( build_desc )
|
||||
elif "variables" in axis:
|
||||
for variables in axis["variables"]:
|
||||
build_desc = BuildDesc( variables=variables )
|
||||
build_descs.append( build_desc )
|
||||
elif "build_types" in axis:
|
||||
for build_type in axis["build_types"]:
|
||||
build_desc = BuildDesc( build_type=build_type )
|
||||
build_descs.append( build_desc )
|
||||
build_descs_by_axis[axis_name].extend( build_descs )
|
||||
return build_descs_by_axis
|
||||
|
||||
def generate_build_variants( build_descs_by_axis ):
|
||||
"""Returns a list of BuildDesc generated for the partial BuildDesc for each axis."""
|
||||
axis_names = build_descs_by_axis.keys()
|
||||
build_descs = []
|
||||
for axis_name, axis_build_descs in build_descs_by_axis.items():
|
||||
if len(build_descs):
|
||||
# for each existing build_desc and each axis build desc, create a new build_desc
|
||||
new_build_descs = []
|
||||
for prototype_build_desc, axis_build_desc in itertools.product( build_descs, axis_build_descs):
|
||||
new_build_descs.append( prototype_build_desc.merged_with( axis_build_desc ) )
|
||||
build_descs = new_build_descs
|
||||
else:
|
||||
build_descs = axis_build_descs
|
||||
return build_descs
|
||||
|
||||
HTML_TEMPLATE = string.Template('''<html>
|
||||
<head>
|
||||
<title>$title</title>
|
||||
<style type="text/css">
|
||||
td.failed {background-color:#f08080;}
|
||||
td.ok {background-color:#c0eec0;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variables</th>
|
||||
$th_vars
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Build type</th>
|
||||
$th_build_types
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
$tr_builds
|
||||
</tbody>
|
||||
</table>
|
||||
</body></html>''')
|
||||
|
||||
def generate_html_report( html_report_path, builds ):
|
||||
report_dir = os.path.dirname( html_report_path )
|
||||
# Vertical axis: generator
|
||||
# Horizontal: variables, then build_type
|
||||
builds_by_generator = collections.defaultdict( list )
|
||||
variables = set()
|
||||
build_types_by_variable = collections.defaultdict( set )
|
||||
build_by_pos_key = {} # { (generator, var_key, build_type): build }
|
||||
for build in builds:
|
||||
builds_by_generator[build.desc.generator].append( build )
|
||||
var_key = tuple(sorted(build.desc.variables))
|
||||
variables.add( var_key )
|
||||
build_types_by_variable[var_key].add( build.desc.build_type )
|
||||
pos_key = (build.desc.generator, var_key, build.desc.build_type)
|
||||
build_by_pos_key[pos_key] = build
|
||||
variables = sorted( variables )
|
||||
th_vars = []
|
||||
th_build_types = []
|
||||
for variable in variables:
|
||||
build_types = sorted( build_types_by_variable[variable] )
|
||||
nb_build_type = len(build_types_by_variable[variable])
|
||||
th_vars.append( '<th colspan="%d">%s</th>' % (nb_build_type, cgi.escape( ' '.join( variable ) ) ) )
|
||||
for build_type in build_types:
|
||||
th_build_types.append( '<th>%s</th>' % cgi.escape(build_type) )
|
||||
tr_builds = []
|
||||
for generator in sorted( builds_by_generator ):
|
||||
tds = [ '<td>%s</td>\n' % cgi.escape( generator ) ]
|
||||
for variable in variables:
|
||||
build_types = sorted( build_types_by_variable[variable] )
|
||||
for build_type in build_types:
|
||||
pos_key = (generator, variable, build_type)
|
||||
build = build_by_pos_key.get(pos_key)
|
||||
if build:
|
||||
cmake_status = 'ok' if build.cmake_succeeded else 'FAILED'
|
||||
build_status = 'ok' if build.build_succeeded else 'FAILED'
|
||||
cmake_log_url = os.path.relpath( build.cmake_log_path, report_dir )
|
||||
build_log_url = os.path.relpath( build.build_log_path, report_dir )
|
||||
td = '<td class="%s"><a href="%s" class="%s">CMake: %s</a>' % (
|
||||
build_status.lower(), cmake_log_url, cmake_status.lower(), cmake_status)
|
||||
if build.cmake_succeeded:
|
||||
td += '<br><a href="%s" class="%s">Build: %s</a>' % (
|
||||
build_log_url, build_status.lower(), build_status)
|
||||
td += '</td>'
|
||||
else:
|
||||
td = '<td></td>'
|
||||
tds.append( td )
|
||||
tr_builds.append( '<tr>%s</tr>' % '\n'.join( tds ) )
|
||||
html = HTML_TEMPLATE.substitute(
|
||||
title='Batch build report',
|
||||
th_vars=' '.join(th_vars),
|
||||
th_build_types=' '.join( th_build_types),
|
||||
tr_builds='\n'.join( tr_builds ) )
|
||||
with open( html_report_path, 'wt' ) as fhtml:
|
||||
fhtml.write( html )
|
||||
print 'HTML report generated in:', html_report_path
|
||||
|
||||
def main():
|
||||
usage = r"""%prog WORK_DIR SOURCE_DIR CONFIG_JSON_PATH [CONFIG2_JSON_PATH...]
|
||||
Build a given CMake based project located in SOURCE_DIR with multiple generators/options.dry_run
|
||||
as described in CONFIG_JSON_PATH building in WORK_DIR.
|
||||
|
||||
Example of call:
|
||||
python devtools\batchbuild.py e:\buildbots\jsoncpp\build . devtools\agent_vmw7.json
|
||||
"""
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser(usage=usage)
|
||||
parser.allow_interspersed_args = True
|
||||
# parser.add_option('-v', '--verbose', dest="verbose", action='store_true',
|
||||
# help="""Be verbose.""")
|
||||
parser.enable_interspersed_args()
|
||||
options, args = parser.parse_args()
|
||||
if len(args) < 3:
|
||||
parser.error( "Missing one of WORK_DIR SOURCE_DIR CONFIG_JSON_PATH." )
|
||||
work_dir = args[0]
|
||||
source_dir = args[1].rstrip('/\\')
|
||||
config_paths = args[2:]
|
||||
for config_path in config_paths:
|
||||
if not os.path.isfile( config_path ):
|
||||
parser.error( "Can not read: %r" % config_path )
|
||||
|
||||
# generate build variants
|
||||
build_descs = []
|
||||
for config_path in config_paths:
|
||||
build_descs_by_axis = load_build_variants_from_config( config_path )
|
||||
build_descs.extend( generate_build_variants( build_descs_by_axis ) )
|
||||
print 'Build variants (%d):' % len(build_descs)
|
||||
# assign build directory for each variant
|
||||
if not os.path.isdir( work_dir ):
|
||||
os.makedirs( work_dir )
|
||||
builds = []
|
||||
with open( os.path.join( work_dir, 'matrix-dir-map.txt' ), 'wt' ) as fmatrixmap:
|
||||
for index, build_desc in enumerate( build_descs ):
|
||||
build_desc_work_dir = os.path.join( work_dir, '%03d' % (index+1) )
|
||||
builds.append( BuildData( build_desc, build_desc_work_dir, source_dir ) )
|
||||
fmatrixmap.write( '%s: %s\n' % (build_desc_work_dir, build_desc) )
|
||||
for build in builds:
|
||||
build.execute_build()
|
||||
html_report_path = os.path.join( work_dir, 'batchbuild-report.html' )
|
||||
generate_html_report( html_report_path, builds )
|
||||
print 'Done'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
@@ -1,14 +1,13 @@
|
||||
from __future__ import print_function
|
||||
import os.path
|
||||
|
||||
def fix_source_eol(path, is_dry_run = True, verbose = True, eol = '\n'):
|
||||
def fix_source_eol( path, is_dry_run = True, verbose = True, eol = '\n' ):
|
||||
"""Makes sure that all sources have the specified eol sequence (default: unix)."""
|
||||
if not os.path.isfile(path):
|
||||
raise ValueError('Path "%s" is not a file' % path)
|
||||
if not os.path.isfile( path ):
|
||||
raise ValueError( 'Path "%s" is not a file' % path )
|
||||
try:
|
||||
f = open(path, 'rb')
|
||||
except IOError as msg:
|
||||
print("%s: I/O Error: %s" % (file, str(msg)), file=sys.stderr)
|
||||
except IOError, msg:
|
||||
print >> sys.stderr, "%s: I/O Error: %s" % (file, str(msg))
|
||||
return False
|
||||
try:
|
||||
raw_lines = f.readlines()
|
||||
@@ -16,7 +15,7 @@ def fix_source_eol(path, is_dry_run = True, verbose = True, eol = '\n'):
|
||||
f.close()
|
||||
fixed_lines = [line.rstrip('\r\n') + eol for line in raw_lines]
|
||||
if raw_lines != fixed_lines:
|
||||
print('%s =>' % path, end=' ')
|
||||
print '%s =>' % path,
|
||||
if not is_dry_run:
|
||||
f = open(path, "wb")
|
||||
try:
|
||||
@@ -24,32 +23,32 @@ def fix_source_eol(path, is_dry_run = True, verbose = True, eol = '\n'):
|
||||
finally:
|
||||
f.close()
|
||||
if verbose:
|
||||
print(is_dry_run and ' NEED FIX' or ' FIXED')
|
||||
print is_dry_run and ' NEED FIX' or ' FIXED'
|
||||
return True
|
||||
##
|
||||
##
|
||||
##
|
||||
##def _do_fix(is_dry_run = True):
|
||||
##def _do_fix( is_dry_run = True ):
|
||||
## from waftools import antglob
|
||||
## python_sources = antglob.glob('.',
|
||||
## python_sources = antglob.glob( '.',
|
||||
## includes = '**/*.py **/wscript **/wscript_build',
|
||||
## excludes = antglob.default_excludes + './waf.py',
|
||||
## prune_dirs = antglob.prune_dirs + 'waf-* ./build')
|
||||
## prune_dirs = antglob.prune_dirs + 'waf-* ./build' )
|
||||
## for path in python_sources:
|
||||
## _fix_python_source(path, is_dry_run)
|
||||
## _fix_python_source( path, is_dry_run )
|
||||
##
|
||||
## cpp_sources = antglob.glob('.',
|
||||
## cpp_sources = antglob.glob( '.',
|
||||
## includes = '**/*.cpp **/*.h **/*.inl',
|
||||
## prune_dirs = antglob.prune_dirs + 'waf-* ./build')
|
||||
## prune_dirs = antglob.prune_dirs + 'waf-* ./build' )
|
||||
## for path in cpp_sources:
|
||||
## _fix_source_eol(path, is_dry_run)
|
||||
## _fix_source_eol( path, is_dry_run )
|
||||
##
|
||||
##
|
||||
##def dry_fix(context):
|
||||
## _do_fix(is_dry_run = True)
|
||||
## _do_fix( is_dry_run = True )
|
||||
##
|
||||
##def fix(context):
|
||||
## _do_fix(is_dry_run = False)
|
||||
## _do_fix( is_dry_run = False )
|
||||
##
|
||||
##def shutdown():
|
||||
## pass
|
||||
|
@@ -1,6 +1,5 @@
|
||||
"""Updates the license text in source file.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
# An existing license is found if the file starts with the string below,
|
||||
# and ends with the first blank line.
|
||||
@@ -13,7 +12,7 @@ BRIEF_LICENSE = LICENSE_BEGIN + """2007-2010 Baptiste Lepilleur
|
||||
|
||||
""".replace('\r\n','\n')
|
||||
|
||||
def update_license(path, dry_run, show_diff):
|
||||
def update_license( path, dry_run, show_diff ):
|
||||
"""Update the license statement in the specified file.
|
||||
Parameters:
|
||||
path: path of the C++ source file to update.
|
||||
@@ -22,28 +21,28 @@ def update_license(path, dry_run, show_diff):
|
||||
show_diff: if True, print the path of the file that would be modified,
|
||||
as well as the change made to the file.
|
||||
"""
|
||||
with open(path, 'rt') as fin:
|
||||
with open( path, 'rt' ) as fin:
|
||||
original_text = fin.read().replace('\r\n','\n')
|
||||
newline = fin.newlines and fin.newlines[0] or '\n'
|
||||
if not original_text.startswith(LICENSE_BEGIN):
|
||||
if not original_text.startswith( LICENSE_BEGIN ):
|
||||
# No existing license found => prepend it
|
||||
new_text = BRIEF_LICENSE + original_text
|
||||
else:
|
||||
license_end_index = original_text.index('\n\n') # search first blank line
|
||||
license_end_index = original_text.index( '\n\n' ) # search first blank line
|
||||
new_text = BRIEF_LICENSE + original_text[license_end_index+2:]
|
||||
if original_text != new_text:
|
||||
if not dry_run:
|
||||
with open(path, 'wb') as fout:
|
||||
fout.write(new_text.replace('\n', newline))
|
||||
print('Updated', path)
|
||||
with open( path, 'wb' ) as fout:
|
||||
fout.write( new_text.replace('\n', newline ) )
|
||||
print 'Updated', path
|
||||
if show_diff:
|
||||
import difflib
|
||||
print('\n'.join(difflib.unified_diff(original_text.split('\n'),
|
||||
new_text.split('\n'))))
|
||||
print '\n'.join( difflib.unified_diff( original_text.split('\n'),
|
||||
new_text.split('\n') ) )
|
||||
return True
|
||||
return False
|
||||
|
||||
def update_license_in_source_directories(source_dirs, dry_run, show_diff):
|
||||
def update_license_in_source_directories( source_dirs, dry_run, show_diff ):
|
||||
"""Updates license text in C++ source files found in directory source_dirs.
|
||||
Parameters:
|
||||
source_dirs: list of directory to scan for C++ sources. Directories are
|
||||
@@ -56,11 +55,11 @@ def update_license_in_source_directories(source_dirs, dry_run, show_diff):
|
||||
from devtools import antglob
|
||||
prune_dirs = antglob.prune_dirs + 'scons-local* ./build* ./libs ./dist'
|
||||
for source_dir in source_dirs:
|
||||
cpp_sources = antglob.glob(source_dir,
|
||||
cpp_sources = antglob.glob( source_dir,
|
||||
includes = '''**/*.h **/*.cpp **/*.inl''',
|
||||
prune_dirs = prune_dirs)
|
||||
prune_dirs = prune_dirs )
|
||||
for source in cpp_sources:
|
||||
update_license(source, dry_run, show_diff)
|
||||
update_license( source, dry_run, show_diff )
|
||||
|
||||
def main():
|
||||
usage = """%prog DIR [DIR2...]
|
||||
@@ -83,8 +82,8 @@ python devtools\licenseupdater.py include src
|
||||
help="""On update, show change made to the file.""")
|
||||
parser.enable_interspersed_args()
|
||||
options, args = parser.parse_args()
|
||||
update_license_in_source_directories(args, options.dry_run, options.show_diff)
|
||||
print('Done')
|
||||
update_license_in_source_directories( args, options.dry_run, options.show_diff )
|
||||
print 'Done'
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from contextlib import closing
|
||||
import os
|
||||
import os.path
|
||||
import gzip
|
||||
import tarfile
|
||||
|
||||
TARGZ_DEFAULT_COMPRESSION_LEVEL = 9
|
||||
@@ -13,35 +13,41 @@ def make_tarball(tarball_path, sources, base_dir, prefix_dir=''):
|
||||
prefix_dir: all files stored in the tarball be sub-directory of prefix_dir. Set to ''
|
||||
to make them child of root.
|
||||
"""
|
||||
base_dir = os.path.normpath(os.path.abspath(base_dir))
|
||||
def archive_name(path):
|
||||
base_dir = os.path.normpath( os.path.abspath( base_dir ) )
|
||||
def archive_name( path ):
|
||||
"""Makes path relative to base_dir."""
|
||||
path = os.path.normpath(os.path.abspath(path))
|
||||
common_path = os.path.commonprefix((base_dir, path))
|
||||
path = os.path.normpath( os.path.abspath( path ) )
|
||||
common_path = os.path.commonprefix( (base_dir, path) )
|
||||
archive_name = path[len(common_path):]
|
||||
if os.path.isabs(archive_name):
|
||||
if os.path.isabs( archive_name ):
|
||||
archive_name = archive_name[1:]
|
||||
return os.path.join(prefix_dir, archive_name)
|
||||
return os.path.join( prefix_dir, archive_name )
|
||||
def visit(tar, dirname, names):
|
||||
for name in names:
|
||||
path = os.path.join(dirname, name)
|
||||
if os.path.isfile(path):
|
||||
path_in_tar = archive_name(path)
|
||||
tar.add(path, path_in_tar)
|
||||
tar.add(path, path_in_tar )
|
||||
compression = TARGZ_DEFAULT_COMPRESSION_LEVEL
|
||||
with closing(tarfile.TarFile.open(tarball_path, 'w:gz',
|
||||
compresslevel=compression)) as tar:
|
||||
tar = tarfile.TarFile.gzopen( tarball_path, 'w', compresslevel=compression )
|
||||
try:
|
||||
for source in sources:
|
||||
source_path = source
|
||||
if os.path.isdir(source):
|
||||
for dirpath, dirnames, filenames in os.walk(source_path):
|
||||
visit(tar, dirpath, filenames)
|
||||
if os.path.isdir( source ):
|
||||
os.path.walk(source_path, visit, tar)
|
||||
else:
|
||||
path_in_tar = archive_name(source_path)
|
||||
tar.add(source_path, path_in_tar) # filename, arcname
|
||||
tar.add(source_path, path_in_tar ) # filename, arcname
|
||||
finally:
|
||||
tar.close()
|
||||
|
||||
def decompress(tarball_path, base_dir):
|
||||
def decompress( tarball_path, base_dir ):
|
||||
"""Decompress the gzipped tarball into directory base_dir.
|
||||
"""
|
||||
with closing(tarfile.TarFile.open(tarball_path)) as tar:
|
||||
tar.extractall(base_dir)
|
||||
# !!! This class method is not documented in the online doc
|
||||
# nor is bz2open!
|
||||
tar = tarfile.TarFile.gzopen(tarball_path, mode='r')
|
||||
try:
|
||||
tar.extractall( base_dir )
|
||||
finally:
|
||||
tar.close()
|
||||
|
@@ -819,7 +819,7 @@ EXCLUDE_SYMBOLS =
|
||||
# that contain example code fragments that are included (see the \include
|
||||
# command).
|
||||
|
||||
EXAMPLE_PATH = ..
|
||||
EXAMPLE_PATH =
|
||||
|
||||
# If the value of the EXAMPLE_PATH tag contains directories, you can use the
|
||||
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
|
||||
|
@@ -16,7 +16,7 @@ JsonCpp - JSON data format manipulation library
|
||||
</a>
|
||||
</td>
|
||||
<td width="40%" align="right" valign="center">
|
||||
<a href="http://open-source-parsers.github.io/jsoncpp-docs/doxygen/">JsonCpp home page</a>
|
||||
<a href="https://github.com/open-source-parsers/jsoncpp">JsonCpp home page</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
125
doc/jsoncpp.dox
125
doc/jsoncpp.dox
@@ -4,21 +4,11 @@
|
||||
|
||||
<a HREF="http://www.json.org/">JSON (JavaScript Object Notation)</a>
|
||||
is a lightweight data-interchange format.
|
||||
It can represent integer, real number, string, an ordered sequence of value, and
|
||||
a collection of name/value pairs.
|
||||
|
||||
Here is an example of JSON data:
|
||||
\verbatim
|
||||
{
|
||||
"encoding" : "UTF-8",
|
||||
"plug-ins" : [
|
||||
"python",
|
||||
"c++",
|
||||
"ruby"
|
||||
],
|
||||
"indent" : { "length" : 3, "use_space": true }
|
||||
}
|
||||
\endverbatim
|
||||
<b>JsonCpp</b> supports comments as <i>meta-data</i>:
|
||||
\code
|
||||
// Configuration options
|
||||
{
|
||||
// Default encoding for text
|
||||
@@ -27,22 +17,22 @@ Here is an example of JSON data:
|
||||
// Plug-ins loaded at start-up
|
||||
"plug-ins" : [
|
||||
"python",
|
||||
"c++", // trailing comment
|
||||
"c++",
|
||||
"ruby"
|
||||
],
|
||||
|
||||
// Tab indent size
|
||||
// (multi-line comment)
|
||||
"indent" : { /*embedded comment*/ "length" : 3, "use_space": true }
|
||||
"indent" : { "length" : 3, "use_space": true }
|
||||
}
|
||||
\endcode
|
||||
\endverbatim
|
||||
<code>jsoncpp</code> supports comments as <i>meta-data</i>.
|
||||
|
||||
\section _features Features
|
||||
- read and write JSON document
|
||||
- attach C++ style comments to element during parsing
|
||||
- rewrite JSON document preserving original comments
|
||||
|
||||
Notes: Comments used to be supported in JSON but were removed for
|
||||
Notes: Comments used to be supported in JSON but where removed for
|
||||
portability (C like comments are not supported in Python). Since
|
||||
comments are useful in configuration/input file, this feature was
|
||||
preserved.
|
||||
@@ -50,77 +40,47 @@ preserved.
|
||||
\section _example Code example
|
||||
|
||||
\code
|
||||
Json::Value root; // 'root' will contain the root value after parsing.
|
||||
std::cin >> root;
|
||||
Json::Value root; // will contains the root value after parsing.
|
||||
Json::Reader reader;
|
||||
bool parsingSuccessful = reader.parse( config_doc, root );
|
||||
if ( !parsingSuccessful )
|
||||
{
|
||||
// report to the user the failure and their locations in the document.
|
||||
std::cout << "Failed to parse configuration\n"
|
||||
<< reader.getFormattedErrorMessages();
|
||||
return;
|
||||
}
|
||||
|
||||
// You can also read into a particular sub-value.
|
||||
std::cin >> root["subtree"];
|
||||
|
||||
// Get the value of the member of root named 'encoding',
|
||||
// and return 'UTF-8' if there is no such member.
|
||||
// Get the value of the member of root named 'encoding', return 'UTF-8' if there is no
|
||||
// such member.
|
||||
std::string encoding = root.get("encoding", "UTF-8" ).asString();
|
||||
|
||||
// Get the value of the member of root named 'plug-ins'; return a 'null' value if
|
||||
// Get the value of the member of root named 'encoding', return a 'null' value if
|
||||
// there is no such member.
|
||||
const Json::Value plugins = root["plug-ins"];
|
||||
|
||||
// Iterate over the sequence elements.
|
||||
for ( int index = 0; index < plugins.size(); ++index )
|
||||
for ( int index = 0; index < plugins.size(); ++index ) // Iterates over the sequence elements.
|
||||
loadPlugIn( plugins[index].asString() );
|
||||
|
||||
// Try other datatypes. Some are auto-convertible to others.
|
||||
foo::setIndentLength( root["indent"].get("length", 3).asInt() );
|
||||
foo::setIndentUseSpace( root["indent"].get("use_space", true).asBool() );
|
||||
setIndentLength( root["indent"].get("length", 3).asInt() );
|
||||
setIndentUseSpace( root["indent"].get("use_space", true).asBool() );
|
||||
|
||||
// Since Json::Value has an implicit constructor for all value types, it is not
|
||||
// necessary to explicitly construct the Json::Value object.
|
||||
root["encoding"] = foo::getCurrentEncoding();
|
||||
root["indent"]["length"] = foo::getCurrentIndentLength();
|
||||
root["indent"]["use_space"] = foo::getCurrentIndentUseSpace();
|
||||
|
||||
// If you like the defaults, you can insert directly into a stream.
|
||||
std::cout << root;
|
||||
// Of course, you can write to `std::ostringstream` if you prefer.
|
||||
|
||||
// If desired, remember to add a linefeed and flush.
|
||||
std::cout << std::endl;
|
||||
\endcode
|
||||
|
||||
\section _advanced Advanced usage
|
||||
|
||||
Configure *builders* to create *readers* and *writers*. For
|
||||
configuration, we use our own `Json::Value` (rather than
|
||||
standard setters/getters) so that we can add
|
||||
features without losing binary-compatibility.
|
||||
|
||||
\code
|
||||
// For convenience, use `writeString()` with a specialized builder.
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder.settings_["indentation"] = "\t"; // simple Json::Value
|
||||
std::string document = Json::writeString(wbuilder, root);
|
||||
|
||||
// Here, using a specialized Builder, we discard comments and
|
||||
// record errors as we parse.
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
rbuilder.settings_["collectComments"] = false; // simple Json::Value
|
||||
std::string errs;
|
||||
bool ok = Json::parseFromStream(rbuilder, std::cin, &root, &errs);
|
||||
\endcode
|
||||
|
||||
Yes, compile-time configuration-checking would be helpful,
|
||||
but `Json::Value` lets you
|
||||
write and read the builder configuration, which is better! In other words,
|
||||
you can configure your JSON parser using JSON.
|
||||
|
||||
CharReaders and StreamWriters are not thread-safe, but they are re-usable.
|
||||
\code
|
||||
Json::CharReaderBuilder rbuilder;
|
||||
cfg >> rbuilder.settings_;
|
||||
std::unique_ptr<Json::CharReader> const reader(rbuilder.newCharReader());
|
||||
reader->parse(start, stop, &value1, &errs);
|
||||
// ...
|
||||
reader->parse(start, stop, &value2, &errs);
|
||||
// etc.
|
||||
// At application shutdown to make the new configuration document:
|
||||
// Since Json::Value has implicit constructor for all value types, it is not
|
||||
// necessary to explicitly construct the Json::Value object:
|
||||
root["encoding"] = getCurrentEncoding();
|
||||
root["indent"]["length"] = getCurrentIndentLength();
|
||||
root["indent"]["use_space"] = getCurrentIndentUseSpace();
|
||||
|
||||
Json::StyledWriter writer;
|
||||
// Make a new JSON document for the configuration. Preserve original comments.
|
||||
std::string outputConfig = writer.write( root );
|
||||
|
||||
// You can also use streams. This will put the contents of any JSON
|
||||
// stream at a particular sub-value, if you'd like.
|
||||
std::cin >> root["subtree"];
|
||||
|
||||
// And you can write to a stream, using the StyledWriter automatically.
|
||||
std::cout << root;
|
||||
\endcode
|
||||
|
||||
\section _pbuild Build instructions
|
||||
@@ -156,9 +116,4 @@ Basically JsonCpp is licensed under MIT license, or public domain if desired
|
||||
and recognized in your jurisdiction.
|
||||
|
||||
\author Baptiste Lepilleur <blep@users.sourceforge.net> (originator)
|
||||
\author Christopher Dunn <cdunn2001@gmail.com> (primary maintainer)
|
||||
\version \include version
|
||||
We make strong guarantees about binary-compatibility, consistent with
|
||||
<a href="http://apr.apache.org/versioning.html">the Apache versioning scheme</a>.
|
||||
\sa version.h
|
||||
*/
|
||||
|
2302
doc/web_doxyfile.in
2302
doc/web_doxyfile.in
File diff suppressed because it is too large
Load Diff
156
doxybuild.py
156
doxybuild.py
@@ -1,27 +1,12 @@
|
||||
"""Script to generate doxygen documentation.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from devtools import tarball
|
||||
from contextlib import contextmanager
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
import re
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
@contextmanager
|
||||
def cd(newdir):
|
||||
"""
|
||||
http://stackoverflow.com/questions/431684/how-do-i-cd-in-python
|
||||
"""
|
||||
prevdir = os.getcwd()
|
||||
os.chdir(newdir)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.chdir(prevdir)
|
||||
from devtools import tarball
|
||||
|
||||
def find_program(*filenames):
|
||||
"""find a program in folders path_lst, and sets env[var]
|
||||
@@ -29,7 +14,7 @@ def find_program(*filenames):
|
||||
@return: the full path of the filename if found, or '' if filename could not be found
|
||||
"""
|
||||
paths = os.environ.get('PATH', '').split(os.pathsep)
|
||||
suffixes = ('win32' in sys.platform) and '.exe .com .bat .cmd' or ''
|
||||
suffixes = ('win32' in sys.platform ) and '.exe .com .bat .cmd' or ''
|
||||
for filename in filenames:
|
||||
for name in [filename+ext for ext in suffixes.split()]:
|
||||
for directory in paths:
|
||||
@@ -43,56 +28,53 @@ def do_subst_in_file(targetfile, sourcefile, dict):
|
||||
For example, if dict is {'%VERSION%': '1.2345', '%BASE%': 'MyProg'},
|
||||
then all instances of %VERSION% in the file will be replaced with 1.2345 etc.
|
||||
"""
|
||||
with open(sourcefile, 'r') as f:
|
||||
try:
|
||||
f = open(sourcefile, 'rb')
|
||||
contents = f.read()
|
||||
for (k,v) in list(dict.items()):
|
||||
f.close()
|
||||
except:
|
||||
print "Can't read source file %s"%sourcefile
|
||||
raise
|
||||
for (k,v) in dict.items():
|
||||
v = v.replace('\\','\\\\')
|
||||
contents = re.sub(k, v, contents)
|
||||
with open(targetfile, 'w') as f:
|
||||
f.write(contents)
|
||||
|
||||
def getstatusoutput(cmd):
|
||||
"""cmd is a list.
|
||||
"""
|
||||
try:
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
output, _ = process.communicate()
|
||||
status = process.returncode
|
||||
f = open(targetfile, 'wb')
|
||||
f.write(contents)
|
||||
f.close()
|
||||
except:
|
||||
status = -1
|
||||
output = traceback.format_exc()
|
||||
return status, output
|
||||
|
||||
def run_cmd(cmd, silent=False):
|
||||
"""Raise exception on failure.
|
||||
"""
|
||||
info = 'Running: %r in %r' %(' '.join(cmd), os.getcwd())
|
||||
print(info)
|
||||
sys.stdout.flush()
|
||||
if silent:
|
||||
status, output = getstatusoutput(cmd)
|
||||
else:
|
||||
status, output = os.system(' '.join(cmd)), ''
|
||||
if status:
|
||||
msg = 'Error while %s ...\n\terror=%d, output="""%s"""' %(info, status, output)
|
||||
raise Exception(msg)
|
||||
|
||||
def assert_is_exe(path):
|
||||
if not path:
|
||||
raise Exception('path is empty.')
|
||||
if not os.path.isfile(path):
|
||||
raise Exception('%r is not a file.' %path)
|
||||
if not os.access(path, os.X_OK):
|
||||
raise Exception('%r is not executable by this user.' %path)
|
||||
print "Can't write target file %s"%targetfile
|
||||
raise
|
||||
|
||||
def run_doxygen(doxygen_path, config_file, working_dir, is_silent):
|
||||
assert_is_exe(doxygen_path)
|
||||
config_file = os.path.abspath(config_file)
|
||||
with cd(working_dir):
|
||||
config_file = os.path.abspath( config_file )
|
||||
doxygen_path = doxygen_path
|
||||
old_cwd = os.getcwd()
|
||||
try:
|
||||
os.chdir( working_dir )
|
||||
cmd = [doxygen_path, config_file]
|
||||
run_cmd(cmd, is_silent)
|
||||
print 'Running:', ' '.join( cmd )
|
||||
try:
|
||||
import subprocess
|
||||
except:
|
||||
if os.system( ' '.join( cmd ) ) != 0:
|
||||
print 'Documentation generation failed'
|
||||
return False
|
||||
else:
|
||||
if is_silent:
|
||||
process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
|
||||
else:
|
||||
process = subprocess.Popen( cmd )
|
||||
stdout, _ = process.communicate()
|
||||
if process.returncode:
|
||||
print 'Documentation generation failed:'
|
||||
print stdout
|
||||
return False
|
||||
return True
|
||||
finally:
|
||||
os.chdir( old_cwd )
|
||||
|
||||
def build_doc(options, make_release=False):
|
||||
def build_doc( options, make_release=False ):
|
||||
if make_release:
|
||||
options.make_tarball = True
|
||||
options.with_dot = True
|
||||
@@ -101,56 +83,56 @@ def build_doc(options, make_release=False):
|
||||
options.open = False
|
||||
options.silent = True
|
||||
|
||||
version = open('version', 'rt').read().strip()
|
||||
version = open('version','rt').read().strip()
|
||||
output_dir = 'dist/doxygen' # relative to doc/doxyfile location.
|
||||
if not os.path.isdir(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
top_dir = os.path.abspath('.')
|
||||
if not os.path.isdir( output_dir ):
|
||||
os.makedirs( output_dir )
|
||||
top_dir = os.path.abspath( '.' )
|
||||
html_output_dirname = 'jsoncpp-api-html-' + version
|
||||
tarball_path = os.path.join('dist', html_output_dirname + '.tar.gz')
|
||||
warning_log_path = os.path.join(output_dir, '../jsoncpp-doxygen-warning.log')
|
||||
html_output_path = os.path.join(output_dir, html_output_dirname)
|
||||
def yesno(bool):
|
||||
tarball_path = os.path.join( 'dist', html_output_dirname + '.tar.gz' )
|
||||
warning_log_path = os.path.join( output_dir, '../jsoncpp-doxygen-warning.log' )
|
||||
html_output_path = os.path.join( output_dir, html_output_dirname )
|
||||
def yesno( bool ):
|
||||
return bool and 'YES' or 'NO'
|
||||
subst_keys = {
|
||||
'%JSONCPP_VERSION%': version,
|
||||
'%DOC_TOPDIR%': '',
|
||||
'%TOPDIR%': top_dir,
|
||||
'%HTML_OUTPUT%': os.path.join('..', output_dir, html_output_dirname),
|
||||
'%HTML_OUTPUT%': os.path.join( '..', output_dir, html_output_dirname ),
|
||||
'%HAVE_DOT%': yesno(options.with_dot),
|
||||
'%DOT_PATH%': os.path.split(options.dot_path)[0],
|
||||
'%HTML_HELP%': yesno(options.with_html_help),
|
||||
'%UML_LOOK%': yesno(options.with_uml_look),
|
||||
'%WARNING_LOG_PATH%': os.path.join('..', warning_log_path)
|
||||
'%WARNING_LOG_PATH%': os.path.join( '..', warning_log_path )
|
||||
}
|
||||
|
||||
if os.path.isdir(output_dir):
|
||||
print('Deleting directory:', output_dir)
|
||||
shutil.rmtree(output_dir)
|
||||
if not os.path.isdir(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
if os.path.isdir( output_dir ):
|
||||
print 'Deleting directory:', output_dir
|
||||
shutil.rmtree( output_dir )
|
||||
if not os.path.isdir( output_dir ):
|
||||
os.makedirs( output_dir )
|
||||
|
||||
do_subst_in_file('doc/doxyfile', options.doxyfile_input_path, subst_keys)
|
||||
run_doxygen(options.doxygen_path, 'doc/doxyfile', 'doc', is_silent=options.silent)
|
||||
do_subst_in_file( 'doc/doxyfile', 'doc/doxyfile.in', subst_keys )
|
||||
ok = run_doxygen( options.doxygen_path, 'doc/doxyfile', 'doc', is_silent=options.silent )
|
||||
if not options.silent:
|
||||
print(open(warning_log_path, 'r').read())
|
||||
print open(warning_log_path, 'rb').read()
|
||||
index_path = os.path.abspath(os.path.join('doc', subst_keys['%HTML_OUTPUT%'], 'index.html'))
|
||||
print('Generated documentation can be found in:')
|
||||
print(index_path)
|
||||
print 'Generated documentation can be found in:'
|
||||
print index_path
|
||||
if options.open:
|
||||
import webbrowser
|
||||
webbrowser.open('file://' + index_path)
|
||||
webbrowser.open( 'file://' + index_path )
|
||||
if options.make_tarball:
|
||||
print('Generating doc tarball to', tarball_path)
|
||||
print 'Generating doc tarball to', tarball_path
|
||||
tarball_sources = [
|
||||
output_dir,
|
||||
'README.md',
|
||||
'README.txt',
|
||||
'LICENSE',
|
||||
'NEWS.txt',
|
||||
'version'
|
||||
]
|
||||
tarball_basedir = os.path.join(output_dir, html_output_dirname)
|
||||
tarball.make_tarball(tarball_path, tarball_sources, tarball_basedir, html_output_dirname)
|
||||
tarball_basedir = os.path.join( output_dir, html_output_dirname )
|
||||
tarball.make_tarball( tarball_path, tarball_sources, tarball_basedir, html_output_dirname )
|
||||
return tarball_path, html_output_dirname
|
||||
|
||||
def main():
|
||||
@@ -169,8 +151,6 @@ def main():
|
||||
help="""Path to GraphViz dot tool. Must be full qualified path. [Default: %default]""")
|
||||
parser.add_option('--doxygen', dest="doxygen_path", action='store', default=find_program('doxygen'),
|
||||
help="""Path to Doxygen tool. [Default: %default]""")
|
||||
parser.add_option('--in', dest="doxyfile_input_path", action='store', default='doc/doxyfile.in',
|
||||
help="""Path to doxygen inputs. [Default: %default]""")
|
||||
parser.add_option('--with-html-help', dest="with_html_help", action='store_true', default=False,
|
||||
help="""Enable generation of Microsoft HTML HELP""")
|
||||
parser.add_option('--no-uml-look', dest="with_uml_look", action='store_false', default=True,
|
||||
@@ -183,7 +163,7 @@ def main():
|
||||
help="""Hides doxygen output""")
|
||||
parser.enable_interspersed_args()
|
||||
options, args = parser.parse_args()
|
||||
build_doc(options)
|
||||
build_doc( options )
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@@ -7,7 +7,6 @@
|
||||
#define CPPTL_JSON_ASSERTIONS_H_INCLUDED
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sstream>
|
||||
|
||||
#if !defined(JSON_IS_AMALGAMATION)
|
||||
#include "config.h"
|
||||
@@ -16,27 +15,27 @@
|
||||
#if JSON_USE_EXCEPTION
|
||||
#include <stdexcept>
|
||||
#define JSON_ASSERT(condition) \
|
||||
if (!(condition)) {throw std::runtime_error( "assert json failed" );} // @todo <= add detail about condition in exception
|
||||
#define JSON_FAIL_MESSAGE(message) do{std::ostringstream oss; oss << message; throw std::runtime_error(oss.str());}while(0)
|
||||
//#define JSON_FAIL_MESSAGE(message) throw std::runtime_error(message)
|
||||
assert(condition); // @todo <= change this into an exception throw
|
||||
#define JSON_FAIL_MESSAGE(message) throw std::runtime_error(message);
|
||||
#else // JSON_USE_EXCEPTION
|
||||
#define JSON_ASSERT(condition) assert(condition);
|
||||
|
||||
// The call to assert() will show the failure message in debug builds. In
|
||||
// release bugs we abort, for a core-dump or debugger.
|
||||
// release bugs we write to invalid memory in order to crash hard, so that a
|
||||
// debugger or crash reporter gets the chance to take over. We still call exit()
|
||||
// afterward in order to tell the compiler that this macro doesn't return.
|
||||
#define JSON_FAIL_MESSAGE(message) \
|
||||
{ \
|
||||
std::ostringstream oss; oss << message; \
|
||||
assert(false && oss.str().c_str()); \
|
||||
abort(); \
|
||||
assert(false&& message); \
|
||||
strcpy(reinterpret_cast<char*>(666), message); \
|
||||
exit(123); \
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
#define JSON_ASSERT_MESSAGE(condition, message) \
|
||||
if (!(condition)) { \
|
||||
JSON_FAIL_MESSAGE(message); \
|
||||
JSON_FAIL_MESSAGE(message) \
|
||||
}
|
||||
|
||||
#endif // CPPTL_JSON_ASSERTIONS_H_INCLUDED
|
||||
|
@@ -44,6 +44,12 @@ public:
|
||||
/// \c true if root must be either an array or an object value. Default: \c
|
||||
/// false.
|
||||
bool strictRoot_;
|
||||
|
||||
/// \c true if dropped null placeholders are allowed. Default: \c false.
|
||||
bool allowDroppedNullPlaceholders_;
|
||||
|
||||
/// \c true if numeric object key are allowed. Default: \c false.
|
||||
bool allowNumericKeys_;
|
||||
};
|
||||
|
||||
} // namespace Json
|
||||
|
@@ -14,7 +14,6 @@
|
||||
#include <iosfwd>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <istream>
|
||||
|
||||
// Disable warning C4251: <data member>: <type> needs to have dll-interface to
|
||||
// be used by...
|
||||
@@ -28,13 +27,24 @@ namespace Json {
|
||||
/** \brief Unserialize a <a HREF="http://www.json.org">JSON</a> document into a
|
||||
*Value.
|
||||
*
|
||||
* \deprecated Use CharReader and CharReaderBuilder.
|
||||
*/
|
||||
class JSON_API Reader {
|
||||
public:
|
||||
typedef char Char;
|
||||
typedef const Char* Location;
|
||||
|
||||
/** \brief An error tagged with where in the JSON text it was encountered.
|
||||
*
|
||||
* The offsets give the [start, limit) range of bytes within the text. Note
|
||||
* that this is bytes, not codepoints.
|
||||
*
|
||||
*/
|
||||
struct StructuredError {
|
||||
size_t offset_start;
|
||||
size_t offset_limit;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
/** \brief Constructs a Reader allowing all features
|
||||
* for parsing.
|
||||
*/
|
||||
@@ -68,7 +78,7 @@ public:
|
||||
document to read.
|
||||
* \param endDoc Pointer on the end of the UTF-8 encoded string of the
|
||||
document to read.
|
||||
* Must be >= beginDoc.
|
||||
\ Must be >= beginDoc.
|
||||
* \param root [out] Contains the root value of the document if it was
|
||||
* successfully parsed.
|
||||
* \param collectComments \c true to collect comment and allow writing them
|
||||
@@ -111,6 +121,38 @@ public:
|
||||
*/
|
||||
std::string getFormattedErrorMessages() const;
|
||||
|
||||
/** \brief Returns a vector of structured erros encounted while parsing.
|
||||
* \return A (possibly empty) vector of StructuredError objects. Currently
|
||||
* only one error can be returned, but the caller should tolerate
|
||||
* multiple
|
||||
* errors. This can occur if the parser recovers from a non-fatal
|
||||
* parse error and then encounters additional errors.
|
||||
*/
|
||||
std::vector<StructuredError> getStructuredErrors() const;
|
||||
|
||||
/** \brief Add a semantic error message.
|
||||
* \param value JSON Value location associated with the error
|
||||
* \param message The error message.
|
||||
* \return \c true if the error was successfully added, \c false if the
|
||||
* Value offset exceeds the document size.
|
||||
*/
|
||||
bool pushError(const Value& value, const std::string& message);
|
||||
|
||||
/** \brief Add a semantic error message with extra context.
|
||||
* \param value JSON Value location associated with the error
|
||||
* \param message The error message.
|
||||
* \param extra Additional JSON Value location to contextualize the error
|
||||
* \return \c true if the error was successfully added, \c false if either
|
||||
* Value offset exceeds the document size.
|
||||
*/
|
||||
bool pushError(const Value& value, const std::string& message, const Value& extra);
|
||||
|
||||
/** \brief Return whether there are any errors.
|
||||
* \return \c true if there are no errors to report \c false if
|
||||
* errors have occurred.
|
||||
*/
|
||||
bool good() const;
|
||||
|
||||
private:
|
||||
enum TokenType {
|
||||
tokenEndOfStream = 0,
|
||||
@@ -145,6 +187,7 @@ private:
|
||||
|
||||
typedef std::deque<ErrorInfo> Errors;
|
||||
|
||||
bool expectToken(TokenType type, Token& token, const char* message);
|
||||
bool readToken(Token& token);
|
||||
void skipSpaces();
|
||||
bool match(Location pattern, int patternLength);
|
||||
@@ -196,124 +239,8 @@ private:
|
||||
std::string commentsBefore_;
|
||||
Features features_;
|
||||
bool collectComments_;
|
||||
}; // Reader
|
||||
|
||||
/** Interface for reading JSON from a char array.
|
||||
*/
|
||||
class JSON_API CharReader {
|
||||
public:
|
||||
virtual ~CharReader() {}
|
||||
/** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a>
|
||||
document.
|
||||
* The document must be a UTF-8 encoded string containing the document to read.
|
||||
*
|
||||
* \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the
|
||||
document to read.
|
||||
* \param endDoc Pointer on the end of the UTF-8 encoded string of the
|
||||
document to read.
|
||||
* Must be >= beginDoc.
|
||||
* \param root [out] Contains the root value of the document if it was
|
||||
* successfully parsed.
|
||||
* \param errs [out] Formatted error messages (if not NULL)
|
||||
* a user friendly string that lists errors in the parsed
|
||||
* document.
|
||||
* \return \c true if the document was successfully parsed, \c false if an
|
||||
error occurred.
|
||||
*/
|
||||
virtual bool parse(
|
||||
char const* beginDoc, char const* endDoc,
|
||||
Value* root, std::string* errs) = 0;
|
||||
|
||||
class Factory {
|
||||
public:
|
||||
/** \brief Allocate a CharReader via operator new().
|
||||
* \throw std::exception if something goes wrong (e.g. invalid settings)
|
||||
*/
|
||||
virtual CharReader* newCharReader() const = 0;
|
||||
}; // Factory
|
||||
}; // CharReader
|
||||
|
||||
/** \brief Build a CharReader implementation.
|
||||
|
||||
\deprecated This is experimental and will be altered before the next release.
|
||||
|
||||
Usage:
|
||||
\code
|
||||
using namespace Json;
|
||||
CharReaderBuilder builder;
|
||||
builder.settings_["collectComments"] = false;
|
||||
Value value;
|
||||
std::string errs;
|
||||
bool ok = parseFromStream(builder, std::cin, &value, &errs);
|
||||
\endcode
|
||||
*/
|
||||
class JSON_API CharReaderBuilder : public CharReader::Factory {
|
||||
public:
|
||||
// Note: We use a Json::Value so that we can add data-members to this class
|
||||
// without a major version bump.
|
||||
/** Configuration of this builder.
|
||||
These are case-sensitive.
|
||||
Available settings (case-sensitive):
|
||||
- `"collectComments": false or true`
|
||||
- true to collect comment and allow writing them
|
||||
back during serialization, false to discard comments.
|
||||
This parameter is ignored if allowComments is false.
|
||||
- `"allowComments": false or true`
|
||||
- true if comments are allowed.
|
||||
- `"strictRoot": false or true`
|
||||
- true if root must be either an array or an object value
|
||||
- `"allowDroppedNullPlaceholders": false or true`
|
||||
- true if dropped null placeholders are allowed. (See StreamWriterBuilder.)
|
||||
- `"allowNumericKeys": false or true`
|
||||
- true if numeric object keys are allowed.
|
||||
- `"stackLimit": integer`
|
||||
- Exceeding stackLimit (recursive depth of `readValue()`) will
|
||||
cause an exception.
|
||||
- This is a security issue (seg-faults caused by deeply nested JSON),
|
||||
so the default is low.
|
||||
- `"failIfExtra": false or true`
|
||||
- If true, `parse()` returns false when extra non-whitespace trails
|
||||
the JSON value in the input string.
|
||||
|
||||
You can examine 'settings_` yourself
|
||||
to see the defaults. You can also write and read them just like any
|
||||
JSON Value.
|
||||
\sa setDefaults()
|
||||
*/
|
||||
Json::Value settings_;
|
||||
|
||||
CharReaderBuilder();
|
||||
virtual ~CharReaderBuilder();
|
||||
|
||||
virtual CharReader* newCharReader() const;
|
||||
|
||||
/** \return true if 'settings' are legal and consistent;
|
||||
* otherwise, indicate bad settings via 'invalid'.
|
||||
*/
|
||||
bool validate(Json::Value* invalid) const;
|
||||
/** Called by ctor, but you can use this to reset settings_.
|
||||
* \pre 'settings' != NULL (but Json::null is fine)
|
||||
* \remark Defaults:
|
||||
* \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode
|
||||
*/
|
||||
static void setDefaults(Json::Value* settings);
|
||||
/** Same as old Features::strictMode().
|
||||
* \pre 'settings' != NULL (but Json::null is fine)
|
||||
* \remark Defaults:
|
||||
* \snippet src/lib_json/json_reader.cpp CharReaderBuilderDefaults
|
||||
*/
|
||||
static void strictMode(Json::Value* settings);
|
||||
};
|
||||
|
||||
/** Consume entire stream and use its begin/end.
|
||||
* Someday we might have a real StreamReader, but for now this
|
||||
* is convenient.
|
||||
*/
|
||||
bool parseFromStream(
|
||||
CharReader::Factory const&,
|
||||
std::istream&,
|
||||
Value* root, std::string* errs);
|
||||
|
||||
/** \brief Read from 'sin' into 'root'.
|
||||
|
||||
Always keep comments from the input JSON.
|
||||
|
@@ -133,11 +133,7 @@ public:
|
||||
typedef Json::LargestUInt LargestUInt;
|
||||
typedef Json::ArrayIndex ArrayIndex;
|
||||
|
||||
static const Value& nullRef;
|
||||
#if !defined(__ARMEL__)
|
||||
/// \deprecated This exists for binary compatibility only. Use nullRef.
|
||||
static const Value null;
|
||||
#endif
|
||||
static const Value& null;
|
||||
/// Minimum signed integer value that can be stored in a Json::Value.
|
||||
static const LargestInt minLargestInt;
|
||||
/// Maximum signed integer value that can be stored in a Json::Value.
|
||||
@@ -239,26 +235,25 @@ Json::Value obj_value(Json::objectValue); // {}
|
||||
Value(const CppTL::ConstString& value);
|
||||
#endif
|
||||
Value(bool value);
|
||||
/// Deep copy.
|
||||
Value(const Value& other);
|
||||
~Value();
|
||||
|
||||
// Deep copy, then swap(other).
|
||||
Value &operator=(const Value &other);
|
||||
/// Swap everything.
|
||||
/// Swap values.
|
||||
/// \note Currently, comments are intentionally not swapped, for
|
||||
/// both logic and efficiency.
|
||||
void swap(Value& other);
|
||||
/// Swap values but leave comments and source offsets in place.
|
||||
void swapPayload(Value& other);
|
||||
|
||||
ValueType type() const;
|
||||
|
||||
/// Compare payload only, not comments etc.
|
||||
bool operator<(const Value& other) const;
|
||||
bool operator<=(const Value& other) const;
|
||||
bool operator>=(const Value& other) const;
|
||||
bool operator>(const Value& other) const;
|
||||
|
||||
bool operator==(const Value& other) const;
|
||||
bool operator!=(const Value& other) const;
|
||||
|
||||
int compare(const Value& other) const;
|
||||
|
||||
const char* asCString() const;
|
||||
@@ -396,24 +391,9 @@ Json::Value obj_value(Json::objectValue); // {}
|
||||
/// \return the removed Value, or null.
|
||||
/// \pre type() is objectValue or nullValue
|
||||
/// \post type() is unchanged
|
||||
/// \deprecated
|
||||
Value removeMember(const char* key);
|
||||
/// Same as removeMember(const char*)
|
||||
/// \deprecated
|
||||
Value removeMember(const std::string& key);
|
||||
/** \brief Remove the named map member.
|
||||
|
||||
Update 'removed' iff removed.
|
||||
\return true iff removed (no exceptions)
|
||||
*/
|
||||
bool removeMember(const char* key, Value* removed);
|
||||
/** \brief Remove the indexed array element.
|
||||
|
||||
O(n) expensive operations.
|
||||
Update 'removed' iff removed.
|
||||
\return true iff removed (no exceptions)
|
||||
*/
|
||||
bool removeIndex(ArrayIndex i, Value* removed);
|
||||
|
||||
/// Return true if the object has a member named key.
|
||||
bool isMember(const char* key) const;
|
||||
@@ -436,10 +416,8 @@ Json::Value obj_value(Json::objectValue); // {}
|
||||
// EnumValues enumValues() const;
|
||||
//# endif
|
||||
|
||||
/// \deprecated Always pass len.
|
||||
void setComment(const char* comment, CommentPlacement placement);
|
||||
/// Comments must be //... or /* ... */
|
||||
void setComment(const char* comment, size_t len, CommentPlacement placement);
|
||||
void setComment(const char* comment, CommentPlacement placement);
|
||||
/// Comments must be //... or /* ... */
|
||||
void setComment(const std::string& comment, CommentPlacement placement);
|
||||
bool hasComment(CommentPlacement placement) const;
|
||||
@@ -454,9 +432,14 @@ Json::Value obj_value(Json::objectValue); // {}
|
||||
iterator begin();
|
||||
iterator end();
|
||||
|
||||
private:
|
||||
void initBasic(ValueType type, bool allocated = false);
|
||||
// Accessors for the [start, limit) range of bytes within the JSON text from
|
||||
// which this value was parsed, if any.
|
||||
void setOffsetStart(size_t start);
|
||||
void setOffsetLimit(size_t limit);
|
||||
size_t getOffsetStart() const;
|
||||
size_t getOffsetLimit() const;
|
||||
|
||||
private:
|
||||
Value& resolveReference(const char* key, bool isStatic);
|
||||
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
@@ -476,7 +459,7 @@ private:
|
||||
CommentInfo();
|
||||
~CommentInfo();
|
||||
|
||||
void setComment(const char* text, size_t len);
|
||||
void setComment(const char* text);
|
||||
|
||||
char* comment_;
|
||||
};
|
||||
@@ -504,12 +487,17 @@ private:
|
||||
#endif
|
||||
} value_;
|
||||
ValueType type_ : 8;
|
||||
unsigned int allocated_ : 1; // Notes: if declared as bool, bitfield is useless.
|
||||
int allocated_ : 1; // Notes: if declared as bool, bitfield is useless.
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
unsigned int itemIsUsed_ : 1; // used by the ValueInternalMap container.
|
||||
unsigned int memberNameIsStatic_ : 1; // used by the ValueInternalMap container.
|
||||
int memberNameIsStatic_ : 1; // used by the ValueInternalMap container.
|
||||
#endif
|
||||
CommentInfo* comments_;
|
||||
|
||||
// [start, limit) byte offsets in the source JSON text from which this Value
|
||||
// was extracted.
|
||||
size_t start_;
|
||||
size_t limit_;
|
||||
};
|
||||
|
||||
/** \brief Experimental and untested: represents an element of the "path" to
|
||||
@@ -937,7 +925,7 @@ public:
|
||||
bool operator!=(const SelfType& other) const { return !isEqual(other); }
|
||||
|
||||
difference_type operator-(const SelfType& other) const {
|
||||
return other.computeDistance(*this);
|
||||
return computeDistance(other);
|
||||
}
|
||||
|
||||
/// Return either the index or the member name of the referenced value as a
|
||||
@@ -1091,14 +1079,6 @@ public:
|
||||
|
||||
} // namespace Json
|
||||
|
||||
|
||||
namespace std {
|
||||
/// Specialize std::swap() for Json::Value.
|
||||
template<>
|
||||
inline void swap(Json::Value& a, Json::Value& b) { a.swap(b); }
|
||||
}
|
||||
|
||||
|
||||
#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
|
||||
#pragma warning(pop)
|
||||
#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
|
||||
|
@@ -4,10 +4,10 @@
|
||||
#ifndef JSON_VERSION_H_INCLUDED
|
||||
# define JSON_VERSION_H_INCLUDED
|
||||
|
||||
# define JSONCPP_VERSION_STRING "0.8.3"
|
||||
# define JSONCPP_VERSION_STRING "0.7.0"
|
||||
# define JSONCPP_VERSION_MAJOR 0
|
||||
# define JSONCPP_VERSION_MINOR 8
|
||||
# define JSONCPP_VERSION_PATCH 3
|
||||
# define JSONCPP_VERSION_MINOR 7
|
||||
# define JSONCPP_VERSION_PATCH 0
|
||||
# define JSONCPP_VERSION_QUALIFIER
|
||||
# define JSONCPP_VERSION_HEXA ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | (JSONCPP_VERSION_PATCH << 8))
|
||||
|
||||
|
@@ -11,7 +11,6 @@
|
||||
#endif // if !defined(JSON_IS_AMALGAMATION)
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <ostream>
|
||||
|
||||
// Disable warning C4251: <data member>: <type> needs to have dll-interface to
|
||||
// be used by...
|
||||
@@ -24,111 +23,7 @@ namespace Json {
|
||||
|
||||
class Value;
|
||||
|
||||
/**
|
||||
|
||||
Usage:
|
||||
\code
|
||||
using namespace Json;
|
||||
void writeToStdout(StreamWriter::Factory const& factory, Value const& value) {
|
||||
std::unique_ptr<StreamWriter> const writer(
|
||||
factory.newStreamWriter());
|
||||
writer->write(value, &std::cout);
|
||||
std::cout << std::endl; // add lf and flush
|
||||
}
|
||||
\endcode
|
||||
*/
|
||||
class JSON_API StreamWriter {
|
||||
protected:
|
||||
std::ostream* sout_; // not owned; will not delete
|
||||
public:
|
||||
StreamWriter();
|
||||
virtual ~StreamWriter();
|
||||
/** Write Value into document as configured in sub-class.
|
||||
Do not take ownership of sout, but maintain a reference during function.
|
||||
\pre sout != NULL
|
||||
\return zero on success
|
||||
\throw std::exception possibly, depending on configuration
|
||||
*/
|
||||
virtual int write(Value const& root, std::ostream* sout) = 0;
|
||||
|
||||
/** \brief A simple abstract factory.
|
||||
*/
|
||||
class JSON_API Factory {
|
||||
public:
|
||||
virtual ~Factory();
|
||||
/** \brief Allocate a CharReader via operator new().
|
||||
* \throw std::exception if something goes wrong (e.g. invalid settings)
|
||||
*/
|
||||
virtual StreamWriter* newStreamWriter() const = 0;
|
||||
}; // Factory
|
||||
}; // StreamWriter
|
||||
|
||||
/** \brief Write into stringstream, then return string, for convenience.
|
||||
* A StreamWriter will be created from the factory, used, and then deleted.
|
||||
*/
|
||||
std::string writeString(StreamWriter::Factory const& factory, Value const& root);
|
||||
|
||||
|
||||
/** \brief Build a StreamWriter implementation.
|
||||
|
||||
Usage:
|
||||
\code
|
||||
using namespace Json;
|
||||
Value value = ...;
|
||||
StreamWriterBuilder builder;
|
||||
builder.settings_["commentStyle"] = "None";
|
||||
builder.settings_["indentation"] = " "; // or whatever you like
|
||||
std::unique_ptr<Json::StreamWriter> writer(
|
||||
builder.newStreamWriter());
|
||||
writer->write(value, &std::cout);
|
||||
std::cout << std::endl; // add lf and flush
|
||||
\endcode
|
||||
*/
|
||||
class JSON_API StreamWriterBuilder : public StreamWriter::Factory {
|
||||
public:
|
||||
// Note: We use a Json::Value so that we can add data-members to this class
|
||||
// without a major version bump.
|
||||
/** Configuration of this builder.
|
||||
Available settings (case-sensitive):
|
||||
- "commentStyle": "None" or "All"
|
||||
- "indentation": "<anything>"
|
||||
- "enableYAMLCompatibility": false or true
|
||||
- slightly change the whitespace around colons
|
||||
- "dropNullPlaceholders": false or true
|
||||
- Drop the "null" string from the writer's output for nullValues.
|
||||
Strictly speaking, this is not valid JSON. But when the output is being
|
||||
fed to a browser's Javascript, it makes for smaller output and the
|
||||
browser can handle the output just fine.
|
||||
|
||||
You can examine 'settings_` yourself
|
||||
to see the defaults. You can also write and read them just like any
|
||||
JSON Value.
|
||||
\sa setDefaults()
|
||||
*/
|
||||
Json::Value settings_;
|
||||
|
||||
StreamWriterBuilder();
|
||||
virtual ~StreamWriterBuilder();
|
||||
|
||||
/**
|
||||
* \throw std::exception if something goes wrong (e.g. invalid settings)
|
||||
*/
|
||||
virtual StreamWriter* newStreamWriter() const;
|
||||
|
||||
/** \return true if 'settings' are legal and consistent;
|
||||
* otherwise, indicate bad settings via 'invalid'.
|
||||
*/
|
||||
bool validate(Json::Value* invalid) const;
|
||||
/** Called by ctor, but you can use this to reset settings_.
|
||||
* \pre 'settings' != NULL (but Json::null is fine)
|
||||
* \remark Defaults:
|
||||
* \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults
|
||||
*/
|
||||
static void setDefaults(Json::Value* settings);
|
||||
};
|
||||
|
||||
/** \brief Abstract class for writers.
|
||||
* \deprecated Use StreamWriter.
|
||||
*/
|
||||
class JSON_API Writer {
|
||||
public:
|
||||
@@ -144,7 +39,6 @@ public:
|
||||
*consumption,
|
||||
* but may be usefull to support feature such as RPC where bandwith is limited.
|
||||
* \sa Reader, Value
|
||||
* \deprecated Use StreamWriterBuilder.
|
||||
*/
|
||||
class JSON_API FastWriter : public Writer {
|
||||
public:
|
||||
@@ -153,6 +47,15 @@ public:
|
||||
|
||||
void enableYAMLCompatibility();
|
||||
|
||||
/** \brief Drop the "null" string from the writer's output for nullValues.
|
||||
* Strictly speaking, this is not valid JSON. But when the output is being
|
||||
* fed to a browser's Javascript, it makes for smaller output and the
|
||||
* browser can handle the output just fine.
|
||||
*/
|
||||
void dropNullPlaceholders();
|
||||
|
||||
void omitEndingLineFeed();
|
||||
|
||||
public: // overridden from Writer
|
||||
virtual std::string write(const Value& root);
|
||||
|
||||
@@ -161,6 +64,8 @@ private:
|
||||
|
||||
std::string document_;
|
||||
bool yamlCompatiblityEnabled_;
|
||||
bool dropNullPlaceholders_;
|
||||
bool omitEndingLineFeed_;
|
||||
};
|
||||
|
||||
/** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> format in a
|
||||
@@ -185,7 +90,6 @@ private:
|
||||
*#CommentPlacement.
|
||||
*
|
||||
* \sa Reader, Value, Value::setComment()
|
||||
* \deprecated Use StreamWriterBuilder.
|
||||
*/
|
||||
class JSON_API StyledWriter : public Writer {
|
||||
public:
|
||||
@@ -247,7 +151,6 @@ private:
|
||||
*
|
||||
* \param indentation Each level will be indented by this amount extra.
|
||||
* \sa Reader, Value, Value::setComment()
|
||||
* \deprecated Use StreamWriterBuilder.
|
||||
*/
|
||||
class JSON_API StyledStreamWriter {
|
||||
public:
|
||||
@@ -284,8 +187,7 @@ private:
|
||||
std::string indentString_;
|
||||
int rightMargin_;
|
||||
std::string indentation_;
|
||||
bool addChildValues_ : 1;
|
||||
bool indented_ : 1;
|
||||
bool addChildValues_;
|
||||
};
|
||||
|
||||
#if defined(JSON_HAS_INT64)
|
||||
|
249
makerelease.py
249
makerelease.py
@@ -14,7 +14,6 @@ python makerelease.py 0.5.0 0.6.0-dev
|
||||
Note: This was for Subversion. Now that we are in GitHub, we do not
|
||||
need to build versioned tarballs anymore, so makerelease.py is defunct.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
import os.path
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -34,140 +33,140 @@ SVN_TAG_ROOT = SVN_ROOT + 'tags/jsoncpp'
|
||||
SCONS_LOCAL_URL = 'http://sourceforge.net/projects/scons/files/scons-local/1.2.0/scons-local-1.2.0.tar.gz/download'
|
||||
SOURCEFORGE_PROJECT = 'jsoncpp'
|
||||
|
||||
def set_version(version):
|
||||
def set_version( version ):
|
||||
with open('version','wb') as f:
|
||||
f.write(version.strip())
|
||||
f.write( version.strip() )
|
||||
|
||||
def rmdir_if_exist(dir_path):
|
||||
if os.path.isdir(dir_path):
|
||||
shutil.rmtree(dir_path)
|
||||
def rmdir_if_exist( dir_path ):
|
||||
if os.path.isdir( dir_path ):
|
||||
shutil.rmtree( dir_path )
|
||||
|
||||
class SVNError(Exception):
|
||||
pass
|
||||
|
||||
def svn_command(command, *args):
|
||||
def svn_command( command, *args ):
|
||||
cmd = ['svn', '--non-interactive', command] + list(args)
|
||||
print('Running:', ' '.join(cmd))
|
||||
process = subprocess.Popen(cmd,
|
||||
print 'Running:', ' '.join( cmd )
|
||||
process = subprocess.Popen( cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
stderr=subprocess.STDOUT )
|
||||
stdout = process.communicate()[0]
|
||||
if process.returncode:
|
||||
error = SVNError('SVN command failed:\n' + stdout)
|
||||
error = SVNError( 'SVN command failed:\n' + stdout )
|
||||
error.returncode = process.returncode
|
||||
raise error
|
||||
return stdout
|
||||
|
||||
def check_no_pending_commit():
|
||||
"""Checks that there is no pending commit in the sandbox."""
|
||||
stdout = svn_command('status', '--xml')
|
||||
etree = ElementTree.fromstring(stdout)
|
||||
stdout = svn_command( 'status', '--xml' )
|
||||
etree = ElementTree.fromstring( stdout )
|
||||
msg = []
|
||||
for entry in etree.getiterator('entry'):
|
||||
for entry in etree.getiterator( 'entry' ):
|
||||
path = entry.get('path')
|
||||
status = entry.find('wc-status').get('item')
|
||||
if status != 'unversioned' and path != 'version':
|
||||
msg.append('File "%s" has pending change (status="%s")' % (path, status))
|
||||
msg.append( 'File "%s" has pending change (status="%s")' % (path, status) )
|
||||
if msg:
|
||||
msg.insert(0, 'Pending change to commit found in sandbox. Commit them first!')
|
||||
return '\n'.join(msg)
|
||||
msg.insert(0, 'Pending change to commit found in sandbox. Commit them first!' )
|
||||
return '\n'.join( msg )
|
||||
|
||||
def svn_join_url(base_url, suffix):
|
||||
def svn_join_url( base_url, suffix ):
|
||||
if not base_url.endswith('/'):
|
||||
base_url += '/'
|
||||
if suffix.startswith('/'):
|
||||
suffix = suffix[1:]
|
||||
return base_url + suffix
|
||||
|
||||
def svn_check_if_tag_exist(tag_url):
|
||||
def svn_check_if_tag_exist( tag_url ):
|
||||
"""Checks if a tag exist.
|
||||
Returns: True if the tag exist, False otherwise.
|
||||
"""
|
||||
try:
|
||||
list_stdout = svn_command('list', tag_url)
|
||||
except SVNError as e:
|
||||
list_stdout = svn_command( 'list', tag_url )
|
||||
except SVNError, e:
|
||||
if e.returncode != 1 or not str(e).find('tag_url'):
|
||||
raise e
|
||||
# otherwise ignore error, meaning tag does not exist
|
||||
return False
|
||||
return True
|
||||
|
||||
def svn_commit(message):
|
||||
def svn_commit( message ):
|
||||
"""Commit the sandbox, providing the specified comment.
|
||||
"""
|
||||
svn_command('ci', '-m', message)
|
||||
svn_command( 'ci', '-m', message )
|
||||
|
||||
def svn_tag_sandbox(tag_url, message):
|
||||
def svn_tag_sandbox( tag_url, message ):
|
||||
"""Makes a tag based on the sandbox revisions.
|
||||
"""
|
||||
svn_command('copy', '-m', message, '.', tag_url)
|
||||
svn_command( 'copy', '-m', message, '.', tag_url )
|
||||
|
||||
def svn_remove_tag(tag_url, message):
|
||||
def svn_remove_tag( tag_url, message ):
|
||||
"""Removes an existing tag.
|
||||
"""
|
||||
svn_command('delete', '-m', message, tag_url)
|
||||
svn_command( 'delete', '-m', message, tag_url )
|
||||
|
||||
def svn_export(tag_url, export_dir):
|
||||
def svn_export( tag_url, export_dir ):
|
||||
"""Exports the tag_url revision to export_dir.
|
||||
Target directory, including its parent is created if it does not exist.
|
||||
If the directory export_dir exist, it is deleted before export proceed.
|
||||
"""
|
||||
rmdir_if_exist(export_dir)
|
||||
svn_command('export', tag_url, export_dir)
|
||||
rmdir_if_exist( export_dir )
|
||||
svn_command( 'export', tag_url, export_dir )
|
||||
|
||||
def fix_sources_eol(dist_dir):
|
||||
def fix_sources_eol( dist_dir ):
|
||||
"""Set file EOL for tarball distribution.
|
||||
"""
|
||||
print('Preparing exported source file EOL for distribution...')
|
||||
print 'Preparing exported source file EOL for distribution...'
|
||||
prune_dirs = antglob.prune_dirs + 'scons-local* ./build* ./libs ./dist'
|
||||
win_sources = antglob.glob(dist_dir,
|
||||
win_sources = antglob.glob( dist_dir,
|
||||
includes = '**/*.sln **/*.vcproj',
|
||||
prune_dirs = prune_dirs)
|
||||
unix_sources = antglob.glob(dist_dir,
|
||||
prune_dirs = prune_dirs )
|
||||
unix_sources = antglob.glob( dist_dir,
|
||||
includes = '''**/*.h **/*.cpp **/*.inl **/*.txt **/*.dox **/*.py **/*.html **/*.in
|
||||
sconscript *.json *.expected AUTHORS LICENSE''',
|
||||
excludes = antglob.default_excludes + 'scons.py sconsign.py scons-*',
|
||||
prune_dirs = prune_dirs)
|
||||
prune_dirs = prune_dirs )
|
||||
for path in win_sources:
|
||||
fixeol.fix_source_eol(path, is_dry_run = False, verbose = True, eol = '\r\n')
|
||||
fixeol.fix_source_eol( path, is_dry_run = False, verbose = True, eol = '\r\n' )
|
||||
for path in unix_sources:
|
||||
fixeol.fix_source_eol(path, is_dry_run = False, verbose = True, eol = '\n')
|
||||
fixeol.fix_source_eol( path, is_dry_run = False, verbose = True, eol = '\n' )
|
||||
|
||||
def download(url, target_path):
|
||||
def download( url, target_path ):
|
||||
"""Download file represented by url to target_path.
|
||||
"""
|
||||
f = urllib2.urlopen(url)
|
||||
f = urllib2.urlopen( url )
|
||||
try:
|
||||
data = f.read()
|
||||
finally:
|
||||
f.close()
|
||||
fout = open(target_path, 'wb')
|
||||
fout = open( target_path, 'wb' )
|
||||
try:
|
||||
fout.write(data)
|
||||
fout.write( data )
|
||||
finally:
|
||||
fout.close()
|
||||
|
||||
def check_compile(distcheck_top_dir, platform):
|
||||
def check_compile( distcheck_top_dir, platform ):
|
||||
cmd = [sys.executable, 'scons.py', 'platform=%s' % platform, 'check']
|
||||
print('Running:', ' '.join(cmd))
|
||||
log_path = os.path.join(distcheck_top_dir, 'build-%s.log' % platform)
|
||||
flog = open(log_path, 'wb')
|
||||
print 'Running:', ' '.join( cmd )
|
||||
log_path = os.path.join( distcheck_top_dir, 'build-%s.log' % platform )
|
||||
flog = open( log_path, 'wb' )
|
||||
try:
|
||||
process = subprocess.Popen(cmd,
|
||||
process = subprocess.Popen( cmd,
|
||||
stdout=flog,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=distcheck_top_dir)
|
||||
cwd=distcheck_top_dir )
|
||||
stdout = process.communicate()[0]
|
||||
status = (process.returncode == 0)
|
||||
finally:
|
||||
flog.close()
|
||||
return (status, log_path)
|
||||
|
||||
def write_tempfile(content, **kwargs):
|
||||
fd, path = tempfile.mkstemp(**kwargs)
|
||||
f = os.fdopen(fd, 'wt')
|
||||
def write_tempfile( content, **kwargs ):
|
||||
fd, path = tempfile.mkstemp( **kwargs )
|
||||
f = os.fdopen( fd, 'wt' )
|
||||
try:
|
||||
f.write(content)
|
||||
f.write( content )
|
||||
finally:
|
||||
f.close()
|
||||
return path
|
||||
@@ -175,34 +174,34 @@ def write_tempfile(content, **kwargs):
|
||||
class SFTPError(Exception):
|
||||
pass
|
||||
|
||||
def run_sftp_batch(userhost, sftp, batch, retry=0):
|
||||
path = write_tempfile(batch, suffix='.sftp', text=True)
|
||||
def run_sftp_batch( userhost, sftp, batch, retry=0 ):
|
||||
path = write_tempfile( batch, suffix='.sftp', text=True )
|
||||
# psftp -agent -C blep,jsoncpp@web.sourceforge.net -batch -b batch.sftp -bc
|
||||
cmd = [sftp, '-agent', '-C', '-batch', '-b', path, '-bc', userhost]
|
||||
error = None
|
||||
for retry_index in range(0, max(1,retry)):
|
||||
for retry_index in xrange(0, max(1,retry)):
|
||||
heading = retry_index == 0 and 'Running:' or 'Retrying:'
|
||||
print(heading, ' '.join(cmd))
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
print heading, ' '.join( cmd )
|
||||
process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
|
||||
stdout = process.communicate()[0]
|
||||
if process.returncode != 0:
|
||||
error = SFTPError('SFTP batch failed:\n' + stdout)
|
||||
error = SFTPError( 'SFTP batch failed:\n' + stdout )
|
||||
else:
|
||||
break
|
||||
if error:
|
||||
raise error
|
||||
return stdout
|
||||
|
||||
def sourceforge_web_synchro(sourceforge_project, doc_dir,
|
||||
user=None, sftp='sftp'):
|
||||
def sourceforge_web_synchro( sourceforge_project, doc_dir,
|
||||
user=None, sftp='sftp' ):
|
||||
"""Notes: does not synchronize sub-directory of doc-dir.
|
||||
"""
|
||||
userhost = '%s,%s@web.sourceforge.net' % (user, sourceforge_project)
|
||||
stdout = run_sftp_batch(userhost, sftp, """
|
||||
stdout = run_sftp_batch( userhost, sftp, """
|
||||
cd htdocs
|
||||
dir
|
||||
exit
|
||||
""")
|
||||
""" )
|
||||
existing_paths = set()
|
||||
collect = 0
|
||||
for line in stdout.split('\n'):
|
||||
@@ -216,36 +215,36 @@ exit
|
||||
elif collect == 2:
|
||||
path = line.strip().split()[-1:]
|
||||
if path and path[0] not in ('.', '..'):
|
||||
existing_paths.add(path[0])
|
||||
upload_paths = set([os.path.basename(p) for p in antglob.glob(doc_dir)])
|
||||
existing_paths.add( path[0] )
|
||||
upload_paths = set( [os.path.basename(p) for p in antglob.glob( doc_dir )] )
|
||||
paths_to_remove = existing_paths - upload_paths
|
||||
if paths_to_remove:
|
||||
print('Removing the following file from web:')
|
||||
print('\n'.join(paths_to_remove))
|
||||
stdout = run_sftp_batch(userhost, sftp, """cd htdocs
|
||||
print 'Removing the following file from web:'
|
||||
print '\n'.join( paths_to_remove )
|
||||
stdout = run_sftp_batch( userhost, sftp, """cd htdocs
|
||||
rm %s
|
||||
exit""" % ' '.join(paths_to_remove))
|
||||
print('Uploading %d files:' % len(upload_paths))
|
||||
exit""" % ' '.join(paths_to_remove) )
|
||||
print 'Uploading %d files:' % len(upload_paths)
|
||||
batch_size = 10
|
||||
upload_paths = list(upload_paths)
|
||||
start_time = time.time()
|
||||
for index in range(0,len(upload_paths),batch_size):
|
||||
for index in xrange(0,len(upload_paths),batch_size):
|
||||
paths = upload_paths[index:index+batch_size]
|
||||
file_per_sec = (time.time() - start_time) / (index+1)
|
||||
remaining_files = len(upload_paths) - index
|
||||
remaining_sec = file_per_sec * remaining_files
|
||||
print('%d/%d, ETA=%.1fs' % (index+1, len(upload_paths), remaining_sec))
|
||||
run_sftp_batch(userhost, sftp, """cd htdocs
|
||||
print '%d/%d, ETA=%.1fs' % (index+1, len(upload_paths), remaining_sec)
|
||||
run_sftp_batch( userhost, sftp, """cd htdocs
|
||||
lcd %s
|
||||
mput %s
|
||||
exit""" % (doc_dir, ' '.join(paths)), retry=3)
|
||||
exit""" % (doc_dir, ' '.join(paths) ), retry=3 )
|
||||
|
||||
def sourceforge_release_tarball(sourceforge_project, paths, user=None, sftp='sftp'):
|
||||
def sourceforge_release_tarball( sourceforge_project, paths, user=None, sftp='sftp' ):
|
||||
userhost = '%s,%s@frs.sourceforge.net' % (user, sourceforge_project)
|
||||
run_sftp_batch(userhost, sftp, """
|
||||
run_sftp_batch( userhost, sftp, """
|
||||
mput %s
|
||||
exit
|
||||
""" % (' '.join(paths),))
|
||||
""" % (' '.join(paths),) )
|
||||
|
||||
|
||||
def main():
|
||||
@@ -286,99 +285,99 @@ Warning: --force should only be used when developping/testing the release script
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if len(args) != 2:
|
||||
parser.error('release_version missing on command-line.')
|
||||
parser.error( 'release_version missing on command-line.' )
|
||||
release_version = args[0]
|
||||
next_version = args[1]
|
||||
|
||||
if not options.platforms and not options.no_test:
|
||||
parser.error('You must specify either --platform or --no-test option.')
|
||||
parser.error( 'You must specify either --platform or --no-test option.' )
|
||||
|
||||
if options.ignore_pending_commit:
|
||||
msg = ''
|
||||
else:
|
||||
msg = check_no_pending_commit()
|
||||
if not msg:
|
||||
print('Setting version to', release_version)
|
||||
set_version(release_version)
|
||||
svn_commit('Release ' + release_version)
|
||||
tag_url = svn_join_url(SVN_TAG_ROOT, release_version)
|
||||
if svn_check_if_tag_exist(tag_url):
|
||||
print 'Setting version to', release_version
|
||||
set_version( release_version )
|
||||
svn_commit( 'Release ' + release_version )
|
||||
tag_url = svn_join_url( SVN_TAG_ROOT, release_version )
|
||||
if svn_check_if_tag_exist( tag_url ):
|
||||
if options.retag_release:
|
||||
svn_remove_tag(tag_url, 'Overwriting previous tag')
|
||||
svn_remove_tag( tag_url, 'Overwriting previous tag' )
|
||||
else:
|
||||
print('Aborting, tag %s already exist. Use --retag to overwrite it!' % tag_url)
|
||||
sys.exit(1)
|
||||
svn_tag_sandbox(tag_url, 'Release ' + release_version)
|
||||
print 'Aborting, tag %s already exist. Use --retag to overwrite it!' % tag_url
|
||||
sys.exit( 1 )
|
||||
svn_tag_sandbox( tag_url, 'Release ' + release_version )
|
||||
|
||||
print('Generated doxygen document...')
|
||||
print 'Generated doxygen document...'
|
||||
## doc_dirname = r'jsoncpp-api-html-0.5.0'
|
||||
## doc_tarball_path = r'e:\prg\vc\Lib\jsoncpp-trunk\dist\jsoncpp-api-html-0.5.0.tar.gz'
|
||||
doc_tarball_path, doc_dirname = doxybuild.build_doc(options, make_release=True)
|
||||
doc_tarball_path, doc_dirname = doxybuild.build_doc( options, make_release=True )
|
||||
doc_distcheck_dir = 'dist/doccheck'
|
||||
tarball.decompress(doc_tarball_path, doc_distcheck_dir)
|
||||
doc_distcheck_top_dir = os.path.join(doc_distcheck_dir, doc_dirname)
|
||||
tarball.decompress( doc_tarball_path, doc_distcheck_dir )
|
||||
doc_distcheck_top_dir = os.path.join( doc_distcheck_dir, doc_dirname )
|
||||
|
||||
export_dir = 'dist/export'
|
||||
svn_export(tag_url, export_dir)
|
||||
fix_sources_eol(export_dir)
|
||||
svn_export( tag_url, export_dir )
|
||||
fix_sources_eol( export_dir )
|
||||
|
||||
source_dir = 'jsoncpp-src-' + release_version
|
||||
source_tarball_path = 'dist/%s.tar.gz' % source_dir
|
||||
print('Generating source tarball to', source_tarball_path)
|
||||
tarball.make_tarball(source_tarball_path, [export_dir], export_dir, prefix_dir=source_dir)
|
||||
print 'Generating source tarball to', source_tarball_path
|
||||
tarball.make_tarball( source_tarball_path, [export_dir], export_dir, prefix_dir=source_dir )
|
||||
|
||||
amalgamation_tarball_path = 'dist/%s-amalgamation.tar.gz' % source_dir
|
||||
print('Generating amalgamation source tarball to', amalgamation_tarball_path)
|
||||
print 'Generating amalgamation source tarball to', amalgamation_tarball_path
|
||||
amalgamation_dir = 'dist/amalgamation'
|
||||
amalgamate.amalgamate_source(export_dir, '%s/jsoncpp.cpp' % amalgamation_dir, 'json/json.h')
|
||||
amalgamate.amalgamate_source( export_dir, '%s/jsoncpp.cpp' % amalgamation_dir, 'json/json.h' )
|
||||
amalgamation_source_dir = 'jsoncpp-src-amalgamation' + release_version
|
||||
tarball.make_tarball(amalgamation_tarball_path, [amalgamation_dir],
|
||||
amalgamation_dir, prefix_dir=amalgamation_source_dir)
|
||||
tarball.make_tarball( amalgamation_tarball_path, [amalgamation_dir],
|
||||
amalgamation_dir, prefix_dir=amalgamation_source_dir )
|
||||
|
||||
# Decompress source tarball, download and install scons-local
|
||||
distcheck_dir = 'dist/distcheck'
|
||||
distcheck_top_dir = distcheck_dir + '/' + source_dir
|
||||
print('Decompressing source tarball to', distcheck_dir)
|
||||
rmdir_if_exist(distcheck_dir)
|
||||
tarball.decompress(source_tarball_path, distcheck_dir)
|
||||
print 'Decompressing source tarball to', distcheck_dir
|
||||
rmdir_if_exist( distcheck_dir )
|
||||
tarball.decompress( source_tarball_path, distcheck_dir )
|
||||
scons_local_path = 'dist/scons-local.tar.gz'
|
||||
print('Downloading scons-local to', scons_local_path)
|
||||
download(SCONS_LOCAL_URL, scons_local_path)
|
||||
print('Decompressing scons-local to', distcheck_top_dir)
|
||||
tarball.decompress(scons_local_path, distcheck_top_dir)
|
||||
print 'Downloading scons-local to', scons_local_path
|
||||
download( SCONS_LOCAL_URL, scons_local_path )
|
||||
print 'Decompressing scons-local to', distcheck_top_dir
|
||||
tarball.decompress( scons_local_path, distcheck_top_dir )
|
||||
|
||||
# Run compilation
|
||||
print('Compiling decompressed tarball')
|
||||
print 'Compiling decompressed tarball'
|
||||
all_build_status = True
|
||||
for platform in options.platforms.split(','):
|
||||
print('Testing platform:', platform)
|
||||
build_status, log_path = check_compile(distcheck_top_dir, platform)
|
||||
print('see build log:', log_path)
|
||||
print(build_status and '=> ok' or '=> FAILED')
|
||||
print 'Testing platform:', platform
|
||||
build_status, log_path = check_compile( distcheck_top_dir, platform )
|
||||
print 'see build log:', log_path
|
||||
print build_status and '=> ok' or '=> FAILED'
|
||||
all_build_status = all_build_status and build_status
|
||||
if not build_status:
|
||||
print('Testing failed on at least one platform, aborting...')
|
||||
svn_remove_tag(tag_url, 'Removing tag due to failed testing')
|
||||
print 'Testing failed on at least one platform, aborting...'
|
||||
svn_remove_tag( tag_url, 'Removing tag due to failed testing' )
|
||||
sys.exit(1)
|
||||
if options.user:
|
||||
if not options.no_web:
|
||||
print('Uploading documentation using user', options.user)
|
||||
sourceforge_web_synchro(SOURCEFORGE_PROJECT, doc_distcheck_top_dir, user=options.user, sftp=options.sftp)
|
||||
print('Completed documentation upload')
|
||||
print('Uploading source and documentation tarballs for release using user', options.user)
|
||||
sourceforge_release_tarball(SOURCEFORGE_PROJECT,
|
||||
print 'Uploading documentation using user', options.user
|
||||
sourceforge_web_synchro( SOURCEFORGE_PROJECT, doc_distcheck_top_dir, user=options.user, sftp=options.sftp )
|
||||
print 'Completed documentation upload'
|
||||
print 'Uploading source and documentation tarballs for release using user', options.user
|
||||
sourceforge_release_tarball( SOURCEFORGE_PROJECT,
|
||||
[source_tarball_path, doc_tarball_path],
|
||||
user=options.user, sftp=options.sftp)
|
||||
print('Source and doc release tarballs uploaded')
|
||||
user=options.user, sftp=options.sftp )
|
||||
print 'Source and doc release tarballs uploaded'
|
||||
else:
|
||||
print('No upload user specified. Web site and download tarbal were not uploaded.')
|
||||
print('Tarball can be found at:', doc_tarball_path)
|
||||
print 'No upload user specified. Web site and download tarbal were not uploaded.'
|
||||
print 'Tarball can be found at:', doc_tarball_path
|
||||
|
||||
# Set next version number and commit
|
||||
set_version(next_version)
|
||||
svn_commit('Released ' + release_version)
|
||||
set_version( next_version )
|
||||
svn_commit( 'Released ' + release_version )
|
||||
else:
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.stderr.write( msg + '\n' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@@ -1,7 +1,7 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/@LIBRARY_INSTALL_DIR@
|
||||
includedir=${prefix}/@INCLUDE_INSTALL_DIR@
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: jsoncpp
|
||||
Description: A C++ library for interacting with JSON
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import fnmatch
|
||||
import os
|
||||
|
||||
def generate(env):
|
||||
def Glob(env, includes = None, excludes = None, dir = '.'):
|
||||
"""Adds Glob(includes = Split('*'), excludes = None, dir = '.')
|
||||
def generate( env ):
|
||||
def Glob( env, includes = None, excludes = None, dir = '.' ):
|
||||
"""Adds Glob( includes = Split( '*' ), excludes = None, dir = '.')
|
||||
helper function to environment.
|
||||
|
||||
Glob both the file-system files.
|
||||
@@ -12,36 +12,36 @@ def generate(env):
|
||||
excludes: list of file name pattern exluced from the return list.
|
||||
|
||||
Example:
|
||||
sources = env.Glob(("*.cpp", '*.h'), "~*.cpp", "#src")
|
||||
sources = env.Glob( ("*.cpp", '*.h'), "~*.cpp", "#src" )
|
||||
"""
|
||||
def filterFilename(path):
|
||||
abs_path = os.path.join(dir, path)
|
||||
abs_path = os.path.join( dir, path )
|
||||
if not os.path.isfile(abs_path):
|
||||
return 0
|
||||
fn = os.path.basename(path)
|
||||
match = 0
|
||||
for include in includes:
|
||||
if fnmatch.fnmatchcase(fn, include):
|
||||
if fnmatch.fnmatchcase( fn, include ):
|
||||
match = 1
|
||||
break
|
||||
if match == 1 and not excludes is None:
|
||||
for exclude in excludes:
|
||||
if fnmatch.fnmatchcase(fn, exclude):
|
||||
if fnmatch.fnmatchcase( fn, exclude ):
|
||||
match = 0
|
||||
break
|
||||
return match
|
||||
if includes is None:
|
||||
includes = ('*',)
|
||||
elif type(includes) in (type(''), type(u'')):
|
||||
elif type(includes) in ( type(''), type(u'') ):
|
||||
includes = (includes,)
|
||||
if type(excludes) in (type(''), type(u'')):
|
||||
if type(excludes) in ( type(''), type(u'') ):
|
||||
excludes = (excludes,)
|
||||
dir = env.Dir(dir).abspath
|
||||
paths = os.listdir(dir)
|
||||
def makeAbsFileNode(path):
|
||||
return env.File(os.path.join(dir, path))
|
||||
nodes = filter(filterFilename, paths)
|
||||
return map(makeAbsFileNode, nodes)
|
||||
paths = os.listdir( dir )
|
||||
def makeAbsFileNode( path ):
|
||||
return env.File( os.path.join( dir, path ) )
|
||||
nodes = filter( filterFilename, paths )
|
||||
return map( makeAbsFileNode, nodes )
|
||||
|
||||
from SCons.Script import Environment
|
||||
Environment.Glob = Glob
|
||||
|
@@ -47,7 +47,7 @@ import targz
|
||||
## elif token == "=":
|
||||
## data[key] = list()
|
||||
## else:
|
||||
## append_data(data, key, new_data, token)
|
||||
## append_data( data, key, new_data, token )
|
||||
## new_data = True
|
||||
##
|
||||
## last_token = token
|
||||
@@ -55,7 +55,7 @@ import targz
|
||||
##
|
||||
## if last_token == '\\' and token != '\n':
|
||||
## new_data = False
|
||||
## append_data(data, key, new_data, '\\')
|
||||
## append_data( data, key, new_data, '\\' )
|
||||
##
|
||||
## # compress lists of len 1 into single strings
|
||||
## for (k, v) in data.items():
|
||||
@@ -116,7 +116,7 @@ import targz
|
||||
## else:
|
||||
## for pattern in file_patterns:
|
||||
## sources.extend(glob.glob("/".join([node, pattern])))
|
||||
## sources = map(lambda path: env.File(path), sources)
|
||||
## sources = map( lambda path: env.File(path), sources )
|
||||
## return sources
|
||||
##
|
||||
##
|
||||
@@ -143,7 +143,7 @@ def srcDistEmitter(source, target, env):
|
||||
## # add our output locations
|
||||
## for (k, v) in output_formats.items():
|
||||
## if data.get("GENERATE_" + k, v[0]) == "YES":
|
||||
## targets.append(env.Dir(os.path.join(out_dir, data.get(k + "_OUTPUT", v[1]))))
|
||||
## targets.append(env.Dir( os.path.join(out_dir, data.get(k + "_OUTPUT", v[1]))) )
|
||||
##
|
||||
## # don't clobber targets
|
||||
## for node in targets:
|
||||
@@ -161,13 +161,14 @@ def generate(env):
|
||||
Add builders and construction variables for the
|
||||
SrcDist tool.
|
||||
"""
|
||||
## doxyfile_scanner = env.Scanner(## DoxySourceScan,
|
||||
## doxyfile_scanner = env.Scanner(
|
||||
## DoxySourceScan,
|
||||
## "DoxySourceScan",
|
||||
## scan_check = DoxySourceScanCheck,
|
||||
##)
|
||||
## )
|
||||
|
||||
if targz.exists(env):
|
||||
srcdist_builder = targz.makeBuilder(srcDistEmitter)
|
||||
srcdist_builder = targz.makeBuilder( srcDistEmitter )
|
||||
|
||||
env['BUILDERS']['SrcDist'] = srcdist_builder
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import re
|
||||
from SCons.Script import * # the usual scons stuff you get in a SConscript
|
||||
import collections
|
||||
|
||||
def generate(env):
|
||||
"""
|
||||
@@ -26,28 +25,28 @@ def generate(env):
|
||||
contents = f.read()
|
||||
f.close()
|
||||
except:
|
||||
raise SCons.Errors.UserError("Can't read source file %s"%sourcefile)
|
||||
for (k,v) in list(dict.items()):
|
||||
raise SCons.Errors.UserError, "Can't read source file %s"%sourcefile
|
||||
for (k,v) in dict.items():
|
||||
contents = re.sub(k, v, contents)
|
||||
try:
|
||||
f = open(targetfile, 'wb')
|
||||
f.write(contents)
|
||||
f.close()
|
||||
except:
|
||||
raise SCons.Errors.UserError("Can't write target file %s"%targetfile)
|
||||
raise SCons.Errors.UserError, "Can't write target file %s"%targetfile
|
||||
return 0 # success
|
||||
|
||||
def subst_in_file(target, source, env):
|
||||
if 'SUBST_DICT' not in env:
|
||||
raise SCons.Errors.UserError("SubstInFile requires SUBST_DICT to be set.")
|
||||
if not env.has_key('SUBST_DICT'):
|
||||
raise SCons.Errors.UserError, "SubstInFile requires SUBST_DICT to be set."
|
||||
d = dict(env['SUBST_DICT']) # copy it
|
||||
for (k,v) in list(d.items()):
|
||||
if isinstance(v, collections.Callable):
|
||||
for (k,v) in d.items():
|
||||
if callable(v):
|
||||
d[k] = env.subst(v()).replace('\\','\\\\')
|
||||
elif SCons.Util.is_String(v):
|
||||
d[k] = env.subst(v).replace('\\','\\\\')
|
||||
else:
|
||||
raise SCons.Errors.UserError("SubstInFile: key %s: %s must be a string or callable"%(k, repr(v)))
|
||||
raise SCons.Errors.UserError, "SubstInFile: key %s: %s must be a string or callable"%(k, repr(v))
|
||||
for (t,s) in zip(target, source):
|
||||
return do_subst_in_file(str(t), str(s), d)
|
||||
|
||||
@@ -61,8 +60,8 @@ def generate(env):
|
||||
Returns original target, source tuple unchanged.
|
||||
"""
|
||||
d = env['SUBST_DICT'].copy() # copy it
|
||||
for (k,v) in list(d.items()):
|
||||
if isinstance(v, collections.Callable):
|
||||
for (k,v) in d.items():
|
||||
if callable(v):
|
||||
d[k] = env.subst(v())
|
||||
elif SCons.Util.is_String(v):
|
||||
d[k]=env.subst(v)
|
||||
@@ -70,7 +69,7 @@ def generate(env):
|
||||
return target, source
|
||||
|
||||
## env.Append(TOOLS = 'substinfile') # this should be automaticaly done by Scons ?!?
|
||||
subst_action = SCons.Action.Action(subst_in_file, subst_in_file_string)
|
||||
subst_action = SCons.Action.Action( subst_in_file, subst_in_file_string )
|
||||
env['BUILDERS']['SubstInFile'] = Builder(action=subst_action, emitter=subst_emitter)
|
||||
|
||||
def exists(env):
|
||||
|
@@ -27,9 +27,9 @@ TARGZ_DEFAULT_COMPRESSION_LEVEL = 9
|
||||
|
||||
if internal_targz:
|
||||
def targz(target, source, env):
|
||||
def archive_name(path):
|
||||
path = os.path.normpath(os.path.abspath(path))
|
||||
common_path = os.path.commonprefix((base_dir, path))
|
||||
def archive_name( path ):
|
||||
path = os.path.normpath( os.path.abspath( path ) )
|
||||
common_path = os.path.commonprefix( (base_dir, path) )
|
||||
archive_name = path[len(common_path):]
|
||||
return archive_name
|
||||
|
||||
@@ -37,23 +37,23 @@ if internal_targz:
|
||||
for name in names:
|
||||
path = os.path.join(dirname, name)
|
||||
if os.path.isfile(path):
|
||||
tar.add(path, archive_name(path))
|
||||
tar.add(path, archive_name(path) )
|
||||
compression = env.get('TARGZ_COMPRESSION_LEVEL',TARGZ_DEFAULT_COMPRESSION_LEVEL)
|
||||
base_dir = os.path.normpath(env.get('TARGZ_BASEDIR', env.Dir('.')).abspath)
|
||||
base_dir = os.path.normpath( env.get('TARGZ_BASEDIR', env.Dir('.')).abspath )
|
||||
target_path = str(target[0])
|
||||
fileobj = gzip.GzipFile(target_path, 'wb', compression)
|
||||
fileobj = gzip.GzipFile( target_path, 'wb', compression )
|
||||
tar = tarfile.TarFile(os.path.splitext(target_path)[0], 'w', fileobj)
|
||||
for source in source:
|
||||
source_path = str(source)
|
||||
if source.isdir():
|
||||
os.path.walk(source_path, visit, tar)
|
||||
else:
|
||||
tar.add(source_path, archive_name(source_path)) # filename, arcname
|
||||
tar.add(source_path, archive_name(source_path) ) # filename, arcname
|
||||
tar.close()
|
||||
|
||||
targzAction = SCons.Action.Action(targz, varlist=['TARGZ_COMPRESSION_LEVEL','TARGZ_BASEDIR'])
|
||||
|
||||
def makeBuilder(emitter = None):
|
||||
def makeBuilder( emitter = None ):
|
||||
return SCons.Builder.Builder(action = SCons.Action.Action('$TARGZ_COM', '$TARGZ_COMSTR'),
|
||||
source_factory = SCons.Node.FS.Entry,
|
||||
source_scanner = SCons.Defaults.DirScanner,
|
||||
|
@@ -7,13 +7,7 @@ ENDIF(JSONCPP_LIB_BUILD_SHARED)
|
||||
ADD_EXECUTABLE(jsontestrunner_exe
|
||||
main.cpp
|
||||
)
|
||||
|
||||
IF(JSONCPP_LIB_BUILD_SHARED)
|
||||
TARGET_LINK_LIBRARIES(jsontestrunner_exe jsoncpp_lib)
|
||||
ELSE(JSONCPP_LIB_BUILD_SHARED)
|
||||
TARGET_LINK_LIBRARIES(jsontestrunner_exe jsoncpp_lib_static)
|
||||
ENDIF(JSONCPP_LIB_BUILD_SHARED)
|
||||
|
||||
TARGET_LINK_LIBRARIES(jsontestrunner_exe jsoncpp_lib)
|
||||
SET_TARGET_PROPERTIES(jsontestrunner_exe PROPERTIES OUTPUT_NAME jsontestrunner_exe)
|
||||
|
||||
IF(PYTHONINTERP_FOUND)
|
||||
|
@@ -8,22 +8,12 @@
|
||||
|
||||
#include <json/json.h>
|
||||
#include <algorithm> // sort
|
||||
#include <sstream>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1310
|
||||
#pragma warning(disable : 4996) // disable fopen deprecation warning
|
||||
#endif
|
||||
|
||||
struct Options
|
||||
{
|
||||
std::string path;
|
||||
Json::Features features;
|
||||
bool parseOnly;
|
||||
typedef std::string (*writeFuncType)(Json::Value const&);
|
||||
writeFuncType write;
|
||||
};
|
||||
|
||||
static std::string normalizeFloatingPointStr(double value) {
|
||||
char buffer[32];
|
||||
#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__)
|
||||
@@ -139,67 +129,43 @@ printValueTree(FILE* fout, Json::Value& value, const std::string& path = ".") {
|
||||
static int parseAndSaveValueTree(const std::string& input,
|
||||
const std::string& actual,
|
||||
const std::string& kind,
|
||||
Json::Value& root,
|
||||
const Json::Features& features,
|
||||
bool parseOnly,
|
||||
Json::Value* root)
|
||||
{
|
||||
bool parseOnly) {
|
||||
Json::Reader reader(features);
|
||||
bool parsingSuccessful = reader.parse(input, *root);
|
||||
bool parsingSuccessful = reader.parse(input, root);
|
||||
if (!parsingSuccessful) {
|
||||
printf("Failed to parse %s file: \n%s\n",
|
||||
kind.c_str(),
|
||||
reader.getFormattedErrorMessages().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!parseOnly) {
|
||||
FILE* factual = fopen(actual.c_str(), "wt");
|
||||
if (!factual) {
|
||||
printf("Failed to create %s actual file.\n", kind.c_str());
|
||||
return 2;
|
||||
}
|
||||
printValueTree(factual, *root);
|
||||
printValueTree(factual, root);
|
||||
fclose(factual);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// static std::string useFastWriter(Json::Value const& root) {
|
||||
// Json::FastWriter writer;
|
||||
// writer.enableYAMLCompatibility();
|
||||
// return writer.write(root);
|
||||
// }
|
||||
static std::string useStyledWriter(
|
||||
Json::Value const& root)
|
||||
{
|
||||
|
||||
static int rewriteValueTree(const std::string& rewritePath,
|
||||
const Json::Value& root,
|
||||
std::string& rewrite) {
|
||||
// Json::FastWriter writer;
|
||||
// writer.enableYAMLCompatibility();
|
||||
Json::StyledWriter writer;
|
||||
return writer.write(root);
|
||||
}
|
||||
static std::string useStyledStreamWriter(
|
||||
Json::Value const& root)
|
||||
{
|
||||
Json::StyledStreamWriter writer;
|
||||
std::ostringstream sout;
|
||||
writer.write(sout, root);
|
||||
return sout.str();
|
||||
}
|
||||
static std::string useBuiltStyledStreamWriter(
|
||||
Json::Value const& root)
|
||||
{
|
||||
Json::StreamWriterBuilder builder;
|
||||
return Json::writeString(builder, root);
|
||||
}
|
||||
static int rewriteValueTree(
|
||||
const std::string& rewritePath,
|
||||
const Json::Value& root,
|
||||
Options::writeFuncType write,
|
||||
std::string* rewrite)
|
||||
{
|
||||
*rewrite = write(root);
|
||||
rewrite = writer.write(root);
|
||||
FILE* fout = fopen(rewritePath.c_str(), "wt");
|
||||
if (!fout) {
|
||||
printf("Failed to create rewrite file: %s\n", rewritePath.c_str());
|
||||
return 2;
|
||||
}
|
||||
fprintf(fout, "%s\n", rewrite->c_str());
|
||||
fprintf(fout, "%s\n", rewrite.c_str());
|
||||
fclose(fout);
|
||||
return 0;
|
||||
}
|
||||
@@ -228,98 +194,84 @@ static int printUsage(const char* argv[]) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
static int parseCommandLine(
|
||||
int argc, const char* argv[], Options* opts)
|
||||
{
|
||||
opts->parseOnly = false;
|
||||
opts->write = &useStyledWriter;
|
||||
int parseCommandLine(int argc,
|
||||
const char* argv[],
|
||||
Json::Features& features,
|
||||
std::string& path,
|
||||
bool& parseOnly) {
|
||||
parseOnly = false;
|
||||
if (argc < 2) {
|
||||
return printUsage(argv);
|
||||
}
|
||||
|
||||
int index = 1;
|
||||
if (std::string(argv[index]) == "--json-checker") {
|
||||
opts->features = Json::Features::strictMode();
|
||||
opts->parseOnly = true;
|
||||
if (std::string(argv[1]) == "--json-checker") {
|
||||
features = Json::Features::strictMode();
|
||||
parseOnly = true;
|
||||
++index;
|
||||
}
|
||||
if (std::string(argv[index]) == "--json-config") {
|
||||
|
||||
if (std::string(argv[1]) == "--json-config") {
|
||||
printConfig();
|
||||
return 3;
|
||||
}
|
||||
if (std::string(argv[index]) == "--json-writer") {
|
||||
++index;
|
||||
std::string const writerName(argv[index++]);
|
||||
if (writerName == "StyledWriter") {
|
||||
opts->write = &useStyledWriter;
|
||||
} else if (writerName == "StyledStreamWriter") {
|
||||
opts->write = &useStyledStreamWriter;
|
||||
} else if (writerName == "BuiltStyledStreamWriter") {
|
||||
opts->write = &useBuiltStyledStreamWriter;
|
||||
} else {
|
||||
printf("Unknown '--json-writer %s'\n", writerName.c_str());
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == argc || index + 1 < argc) {
|
||||
return printUsage(argv);
|
||||
}
|
||||
opts->path = argv[index];
|
||||
|
||||
path = argv[index];
|
||||
return 0;
|
||||
}
|
||||
static int runTest(Options const& opts)
|
||||
{
|
||||
int exitCode = 0;
|
||||
|
||||
std::string input = readInputTestFile(opts.path.c_str());
|
||||
if (input.empty()) {
|
||||
printf("Failed to read input or empty input: %s\n", opts.path.c_str());
|
||||
return 3;
|
||||
}
|
||||
|
||||
std::string basePath = removeSuffix(opts.path, ".json");
|
||||
if (!opts.parseOnly && basePath.empty()) {
|
||||
printf("Bad input path. Path does not end with '.expected':\n%s\n",
|
||||
opts.path.c_str());
|
||||
return 3;
|
||||
}
|
||||
|
||||
std::string const actualPath = basePath + ".actual";
|
||||
std::string const rewritePath = basePath + ".rewrite";
|
||||
std::string const rewriteActualPath = basePath + ".actual-rewrite";
|
||||
|
||||
Json::Value root;
|
||||
exitCode = parseAndSaveValueTree(
|
||||
input, actualPath, "input",
|
||||
opts.features, opts.parseOnly, &root);
|
||||
if (exitCode || opts.parseOnly) {
|
||||
return exitCode;
|
||||
}
|
||||
std::string rewrite;
|
||||
exitCode = rewriteValueTree(rewritePath, root, opts.write, &rewrite);
|
||||
if (exitCode) {
|
||||
return exitCode;
|
||||
}
|
||||
Json::Value rewriteRoot;
|
||||
exitCode = parseAndSaveValueTree(
|
||||
rewrite, rewriteActualPath, "rewrite",
|
||||
opts.features, opts.parseOnly, &rewriteRoot);
|
||||
if (exitCode) {
|
||||
return exitCode;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int main(int argc, const char* argv[]) {
|
||||
Options opts;
|
||||
int exitCode = parseCommandLine(argc, argv, &opts);
|
||||
std::string path;
|
||||
Json::Features features;
|
||||
bool parseOnly;
|
||||
int exitCode = parseCommandLine(argc, argv, features, path, parseOnly);
|
||||
if (exitCode != 0) {
|
||||
printf("Failed to parse command-line.");
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
try {
|
||||
return runTest(opts);
|
||||
std::string input = readInputTestFile(path.c_str());
|
||||
if (input.empty()) {
|
||||
printf("Failed to read input or empty input: %s\n", path.c_str());
|
||||
return 3;
|
||||
}
|
||||
|
||||
std::string basePath = removeSuffix(argv[1], ".json");
|
||||
if (!parseOnly && basePath.empty()) {
|
||||
printf("Bad input path. Path does not end with '.expected':\n%s\n",
|
||||
path.c_str());
|
||||
return 3;
|
||||
}
|
||||
|
||||
std::string actualPath = basePath + ".actual";
|
||||
std::string rewritePath = basePath + ".rewrite";
|
||||
std::string rewriteActualPath = basePath + ".actual-rewrite";
|
||||
|
||||
Json::Value root;
|
||||
exitCode = parseAndSaveValueTree(
|
||||
input, actualPath, "input", root, features, parseOnly);
|
||||
if (exitCode == 0 && !parseOnly) {
|
||||
std::string rewrite;
|
||||
exitCode = rewriteValueTree(rewritePath, root, rewrite);
|
||||
if (exitCode == 0) {
|
||||
Json::Value rewriteRoot;
|
||||
exitCode = parseAndSaveValueTree(rewrite,
|
||||
rewriteActualPath,
|
||||
"rewrite",
|
||||
rewriteRoot,
|
||||
features,
|
||||
parseOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
printf("Unhandled exception:\n%s\n", e.what());
|
||||
return 1;
|
||||
exitCode = 1;
|
||||
}
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
@@ -1,19 +1,14 @@
|
||||
OPTION(JSONCPP_LIB_BUILD_SHARED "Build jsoncpp_lib as a shared library." OFF)
|
||||
OPTION(JSONCPP_LIB_BUILD_STATIC "Build jsoncpp_lib static library." ON)
|
||||
IF(JSONCPP_LIB_BUILD_SHARED)
|
||||
SET(JSONCPP_LIB_TYPE SHARED)
|
||||
ADD_DEFINITIONS( -DJSON_DLL_BUILD )
|
||||
ELSE(JSONCPP_LIB_BUILD_SHARED)
|
||||
SET(JSONCPP_LIB_TYPE STATIC)
|
||||
ENDIF(JSONCPP_LIB_BUILD_SHARED)
|
||||
|
||||
IF(BUILD_SHARED_LIBS)
|
||||
SET(JSONCPP_LIB_BUILD_SHARED ON)
|
||||
ENDIF(BUILD_SHARED_LIBS)
|
||||
|
||||
if( CMAKE_COMPILER_IS_GNUCXX )
|
||||
#Get compiler version.
|
||||
execute_process( COMMAND ${CMAKE_CXX_COMPILER} -dumpversion
|
||||
OUTPUT_VARIABLE GNUCXX_VERSION )
|
||||
|
||||
#-Werror=* was introduced -after- GCC 4.1.2
|
||||
if( GNUCXX_VERSION VERSION_GREATER 4.1.2 )
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=strict-aliasing")
|
||||
endif()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=strict-aliasing")
|
||||
endif( CMAKE_COMPILER_IS_GNUCXX )
|
||||
|
||||
SET( JSONCPP_INCLUDE_DIR ../../include )
|
||||
@@ -31,56 +26,32 @@ SET( PUBLIC_HEADERS
|
||||
|
||||
SOURCE_GROUP( "Public API" FILES ${PUBLIC_HEADERS} )
|
||||
|
||||
SET(jsoncpp_sources
|
||||
json_tool.h
|
||||
json_reader.cpp
|
||||
json_batchallocator.h
|
||||
json_valueiterator.inl
|
||||
json_value.cpp
|
||||
json_writer.cpp
|
||||
version.h.in)
|
||||
ADD_LIBRARY( jsoncpp_lib ${JSONCPP_LIB_TYPE}
|
||||
${PUBLIC_HEADERS}
|
||||
json_tool.h
|
||||
json_reader.cpp
|
||||
json_batchallocator.h
|
||||
json_valueiterator.inl
|
||||
json_value.cpp
|
||||
json_writer.cpp
|
||||
version.h.in
|
||||
)
|
||||
SET_TARGET_PROPERTIES( jsoncpp_lib PROPERTIES OUTPUT_NAME jsoncpp )
|
||||
SET_TARGET_PROPERTIES( jsoncpp_lib PROPERTIES VERSION ${JSONCPP_VERSION} SOVERSION ${JSONCPP_VERSION_MAJOR} )
|
||||
|
||||
# Install instructions for this target
|
||||
IF(JSONCPP_WITH_CMAKE_PACKAGE)
|
||||
TARGET_INCLUDE_DIRECTORIES( jsoncpp_lib
|
||||
PUBLIC $<INSTALL_INTERFACE:${INCLUDE_INSTALL_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/${JSON_INCLUDE_DIR}>
|
||||
)
|
||||
SET(INSTALL_EXPORT EXPORT jsoncpp)
|
||||
ELSE(JSONCPP_WITH_CMAKE_PACKAGE)
|
||||
SET(INSTALL_EXPORT)
|
||||
ENDIF(JSONCPP_WITH_CMAKE_PACKAGE)
|
||||
|
||||
IF(JSONCPP_LIB_BUILD_SHARED)
|
||||
ADD_DEFINITIONS( -DJSON_DLL_BUILD )
|
||||
ADD_LIBRARY(jsoncpp_lib SHARED ${PUBLIC_HEADERS} ${jsoncpp_sources})
|
||||
SET_TARGET_PROPERTIES( jsoncpp_lib PROPERTIES VERSION ${JSONCPP_VERSION} SOVERSION ${JSONCPP_VERSION_MAJOR})
|
||||
SET_TARGET_PROPERTIES( jsoncpp_lib PROPERTIES OUTPUT_NAME jsoncpp )
|
||||
|
||||
INSTALL( TARGETS jsoncpp_lib ${INSTALL_EXPORT}
|
||||
INSTALL( TARGETS jsoncpp_lib ${INSTALL_EXPORT}
|
||||
RUNTIME DESTINATION ${RUNTIME_INSTALL_DIR}
|
||||
LIBRARY DESTINATION ${LIBRARY_INSTALL_DIR}
|
||||
ARCHIVE DESTINATION ${ARCHIVE_INSTALL_DIR})
|
||||
|
||||
IF(NOT CMAKE_VERSION VERSION_LESS 2.8.11)
|
||||
TARGET_INCLUDE_DIRECTORIES( jsoncpp_lib PUBLIC
|
||||
$<INSTALL_INTERFACE:${INCLUDE_INSTALL_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/${JSONCPP_INCLUDE_DIR}>)
|
||||
ENDIF(NOT CMAKE_VERSION VERSION_LESS 2.8.11)
|
||||
|
||||
ENDIF()
|
||||
|
||||
IF(JSONCPP_LIB_BUILD_STATIC)
|
||||
ADD_LIBRARY(jsoncpp_lib_static STATIC ${PUBLIC_HEADERS} ${jsoncpp_sources})
|
||||
SET_TARGET_PROPERTIES( jsoncpp_lib_static PROPERTIES VERSION ${JSONCPP_VERSION} SOVERSION ${JSONCPP_VERSION_MAJOR})
|
||||
SET_TARGET_PROPERTIES( jsoncpp_lib_static PROPERTIES OUTPUT_NAME jsoncpp )
|
||||
|
||||
INSTALL( TARGETS jsoncpp_lib_static ${INSTALL_EXPORT}
|
||||
RUNTIME DESTINATION ${RUNTIME_INSTALL_DIR}
|
||||
LIBRARY DESTINATION ${LIBRARY_INSTALL_DIR}
|
||||
ARCHIVE DESTINATION ${ARCHIVE_INSTALL_DIR})
|
||||
|
||||
IF(NOT CMAKE_VERSION VERSION_LESS 2.8.11)
|
||||
TARGET_INCLUDE_DIRECTORIES( jsoncpp_lib_static PUBLIC
|
||||
$<INSTALL_INTERFACE:${INCLUDE_INSTALL_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/${JSONCPP_INCLUDE_DIR}>
|
||||
)
|
||||
ENDIF(NOT CMAKE_VERSION VERSION_LESS 2.8.11)
|
||||
|
||||
ENDIF()
|
||||
ARCHIVE DESTINATION ${ARCHIVE_INSTALL_DIR}
|
||||
)
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -31,13 +31,11 @@ namespace Json {
|
||||
#if defined(__ARMEL__)
|
||||
#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment)))
|
||||
#else
|
||||
// This exists for binary compatibility only. Use nullRef.
|
||||
static const Value null;
|
||||
#define ALIGNAS(byte_alignment)
|
||||
#endif
|
||||
static const unsigned char ALIGNAS(8) kNull[sizeof(Value)] = { 0 };
|
||||
const unsigned char& kNullRef = kNull[0];
|
||||
const Value& Value::nullRef = reinterpret_cast<const Value&>(kNullRef);
|
||||
const Value& Value::null = reinterpret_cast<const Value&>(kNullRef);
|
||||
|
||||
const Int Value::minInt = Int(~(UInt(-1) / 2));
|
||||
const Int Value::maxInt = Int(UInt(-1) / 2);
|
||||
@@ -143,17 +141,15 @@ Value::CommentInfo::~CommentInfo() {
|
||||
releaseStringValue(comment_);
|
||||
}
|
||||
|
||||
void Value::CommentInfo::setComment(const char* text, size_t len) {
|
||||
if (comment_) {
|
||||
void Value::CommentInfo::setComment(const char* text) {
|
||||
if (comment_)
|
||||
releaseStringValue(comment_);
|
||||
comment_ = 0;
|
||||
}
|
||||
JSON_ASSERT(text != 0);
|
||||
JSON_ASSERT_MESSAGE(
|
||||
text[0] == '\0' || text[0] == '/',
|
||||
"in Json::Value::setComment(): Comments must start with /");
|
||||
// It seems that /**/ style comments are acceptable as well.
|
||||
comment_ = duplicateStringValue(text, len);
|
||||
comment_ = duplicateStringValue(text);
|
||||
}
|
||||
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
@@ -179,8 +175,7 @@ Value::CZString::CZString(const CZString& other)
|
||||
? duplicateStringValue(other.cstr_)
|
||||
: other.cstr_),
|
||||
index_(other.cstr_
|
||||
? static_cast<ArrayIndex>(other.index_ == noDuplication
|
||||
? noDuplication : duplicate)
|
||||
? (other.index_ == noDuplication ? noDuplication : duplicate)
|
||||
: other.index_) {}
|
||||
|
||||
Value::CZString::~CZString() {
|
||||
@@ -231,8 +226,14 @@ bool Value::CZString::isStaticString() const { return index_ == noDuplication; }
|
||||
* memset( this, 0, sizeof(Value) )
|
||||
* This optimization is used in ValueInternalMap fast allocator.
|
||||
*/
|
||||
Value::Value(ValueType type) {
|
||||
initBasic(type);
|
||||
Value::Value(ValueType type)
|
||||
: type_(type), allocated_(false)
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
,
|
||||
itemIsUsed_(0)
|
||||
#endif
|
||||
,
|
||||
comments_(0), start_(0), limit_(0) {
|
||||
switch (type) {
|
||||
case nullValue:
|
||||
break;
|
||||
@@ -267,62 +268,130 @@ Value::Value(ValueType type) {
|
||||
}
|
||||
}
|
||||
|
||||
Value::Value(Int value) {
|
||||
initBasic(intValue);
|
||||
Value::Value(UInt value)
|
||||
: type_(uintValue), allocated_(false)
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
,
|
||||
itemIsUsed_(0)
|
||||
#endif
|
||||
,
|
||||
comments_(0), start_(0), limit_(0) {
|
||||
value_.uint_ = value;
|
||||
}
|
||||
|
||||
Value::Value(Int value)
|
||||
: type_(intValue), allocated_(false)
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
,
|
||||
itemIsUsed_(0)
|
||||
#endif
|
||||
,
|
||||
comments_(0), start_(0), limit_(0) {
|
||||
value_.int_ = value;
|
||||
}
|
||||
|
||||
Value::Value(UInt value) {
|
||||
initBasic(uintValue);
|
||||
value_.uint_ = value;
|
||||
}
|
||||
#if defined(JSON_HAS_INT64)
|
||||
Value::Value(Int64 value) {
|
||||
initBasic(intValue);
|
||||
Value::Value(Int64 value)
|
||||
: type_(intValue), allocated_(false)
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
,
|
||||
itemIsUsed_(0)
|
||||
#endif
|
||||
,
|
||||
comments_(0), start_(0), limit_(0) {
|
||||
value_.int_ = value;
|
||||
}
|
||||
Value::Value(UInt64 value) {
|
||||
initBasic(uintValue);
|
||||
|
||||
Value::Value(UInt64 value)
|
||||
: type_(uintValue), allocated_(false)
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
,
|
||||
itemIsUsed_(0)
|
||||
#endif
|
||||
,
|
||||
comments_(0), start_(0), limit_(0) {
|
||||
value_.uint_ = value;
|
||||
}
|
||||
#endif // defined(JSON_HAS_INT64)
|
||||
|
||||
Value::Value(double value) {
|
||||
initBasic(realValue);
|
||||
Value::Value(double value)
|
||||
: type_(realValue), allocated_(false)
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
,
|
||||
itemIsUsed_(0)
|
||||
#endif
|
||||
,
|
||||
comments_(0), start_(0), limit_(0) {
|
||||
value_.real_ = value;
|
||||
}
|
||||
|
||||
Value::Value(const char* value) {
|
||||
initBasic(stringValue, true);
|
||||
Value::Value(const char* value)
|
||||
: type_(stringValue), allocated_(true)
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
,
|
||||
itemIsUsed_(0)
|
||||
#endif
|
||||
,
|
||||
comments_(0), start_(0), limit_(0) {
|
||||
value_.string_ = duplicateStringValue(value);
|
||||
}
|
||||
|
||||
Value::Value(const char* beginValue, const char* endValue) {
|
||||
initBasic(stringValue, true);
|
||||
Value::Value(const char* beginValue, const char* endValue)
|
||||
: type_(stringValue), allocated_(true)
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
,
|
||||
itemIsUsed_(0)
|
||||
#endif
|
||||
,
|
||||
comments_(0), start_(0), limit_(0) {
|
||||
value_.string_ =
|
||||
duplicateStringValue(beginValue, (unsigned int)(endValue - beginValue));
|
||||
}
|
||||
|
||||
Value::Value(const std::string& value) {
|
||||
initBasic(stringValue, true);
|
||||
Value::Value(const std::string& value)
|
||||
: type_(stringValue), allocated_(true)
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
,
|
||||
itemIsUsed_(0)
|
||||
#endif
|
||||
,
|
||||
comments_(0), start_(0), limit_(0) {
|
||||
value_.string_ =
|
||||
duplicateStringValue(value.c_str(), (unsigned int)value.length());
|
||||
}
|
||||
|
||||
Value::Value(const StaticString& value) {
|
||||
initBasic(stringValue);
|
||||
Value::Value(const StaticString& value)
|
||||
: type_(stringValue), allocated_(false)
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
,
|
||||
itemIsUsed_(0)
|
||||
#endif
|
||||
,
|
||||
comments_(0), start_(0), limit_(0) {
|
||||
value_.string_ = const_cast<char*>(value.c_str());
|
||||
}
|
||||
|
||||
#ifdef JSON_USE_CPPTL
|
||||
Value::Value(const CppTL::ConstString& value) {
|
||||
initBasic(stringValue, true);
|
||||
Value::Value(const CppTL::ConstString& value)
|
||||
: type_(stringValue), allocated_(true)
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
,
|
||||
itemIsUsed_(0)
|
||||
#endif
|
||||
,
|
||||
comments_(0), start_(0), limit_(0) {
|
||||
value_.string_ = duplicateStringValue(value, value.length());
|
||||
}
|
||||
#endif
|
||||
|
||||
Value::Value(bool value) {
|
||||
initBasic(booleanValue);
|
||||
Value::Value(bool value)
|
||||
: type_(booleanValue), allocated_(false)
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
,
|
||||
itemIsUsed_(0)
|
||||
#endif
|
||||
,
|
||||
comments_(0), start_(0), limit_(0) {
|
||||
value_.bool_ = value;
|
||||
}
|
||||
|
||||
@@ -333,7 +402,7 @@ Value::Value(const Value& other)
|
||||
itemIsUsed_(0)
|
||||
#endif
|
||||
,
|
||||
comments_(0) {
|
||||
comments_(0), start_(other.start_), limit_(other.limit_) {
|
||||
switch (type_) {
|
||||
case nullValue:
|
||||
case intValue:
|
||||
@@ -372,8 +441,7 @@ Value::Value(const Value& other)
|
||||
for (int comment = 0; comment < numberOfCommentPlacement; ++comment) {
|
||||
const CommentInfo& otherComment = other.comments_[comment];
|
||||
if (otherComment.comment_)
|
||||
comments_[comment].setComment(
|
||||
otherComment.comment_, strlen(otherComment.comment_));
|
||||
comments_[comment].setComment(otherComment.comment_);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -417,7 +485,7 @@ Value &Value::operator=(const Value &other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Value::swapPayload(Value& other) {
|
||||
void Value::swap(Value& other) {
|
||||
ValueType temp = type_;
|
||||
type_ = other.type_;
|
||||
other.type_ = temp;
|
||||
@@ -425,11 +493,8 @@ void Value::swapPayload(Value& other) {
|
||||
int temp2 = allocated_;
|
||||
allocated_ = other.allocated_;
|
||||
other.allocated_ = temp2;
|
||||
}
|
||||
|
||||
void Value::swap(Value& other) {
|
||||
swapPayload(other);
|
||||
std::swap(comments_, other.comments_);
|
||||
std::swap(start_, other.start_);
|
||||
std::swap(limit_, other.limit_);
|
||||
}
|
||||
|
||||
ValueType Value::type() const { return type_; }
|
||||
@@ -804,6 +869,8 @@ void Value::clear() {
|
||||
JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue ||
|
||||
type_ == objectValue,
|
||||
"in Json::Value::clear(): requires complex value");
|
||||
start_ = 0;
|
||||
limit_ = 0;
|
||||
switch (type_) {
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
case arrayValue:
|
||||
@@ -857,7 +924,7 @@ Value& Value::operator[](ArrayIndex index) {
|
||||
if (it != value_.map_->end() && (*it).first == key)
|
||||
return (*it).second;
|
||||
|
||||
ObjectValues::value_type defaultValue(key, nullRef);
|
||||
ObjectValues::value_type defaultValue(key, null);
|
||||
it = value_.map_->insert(it, defaultValue);
|
||||
return (*it).second;
|
||||
#else
|
||||
@@ -877,16 +944,16 @@ const Value& Value::operator[](ArrayIndex index) const {
|
||||
type_ == nullValue || type_ == arrayValue,
|
||||
"in Json::Value::operator[](ArrayIndex)const: requires arrayValue");
|
||||
if (type_ == nullValue)
|
||||
return nullRef;
|
||||
return null;
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
CZString key(index);
|
||||
ObjectValues::const_iterator it = value_.map_->find(key);
|
||||
if (it == value_.map_->end())
|
||||
return nullRef;
|
||||
return null;
|
||||
return (*it).second;
|
||||
#else
|
||||
Value* value = value_.array_->find(index);
|
||||
return value ? *value : nullRef;
|
||||
return value ? *value : null;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -901,15 +968,6 @@ Value& Value::operator[](const char* key) {
|
||||
return resolveReference(key, false);
|
||||
}
|
||||
|
||||
void Value::initBasic(ValueType type, bool allocated) {
|
||||
type_ = type;
|
||||
allocated_ = allocated;
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
itemIsUsed_ = 0;
|
||||
#endif
|
||||
comments_ = 0;
|
||||
}
|
||||
|
||||
Value& Value::resolveReference(const char* key, bool isStatic) {
|
||||
JSON_ASSERT_MESSAGE(
|
||||
type_ == nullValue || type_ == objectValue,
|
||||
@@ -923,7 +981,7 @@ Value& Value::resolveReference(const char* key, bool isStatic) {
|
||||
if (it != value_.map_->end() && (*it).first == actualKey)
|
||||
return (*it).second;
|
||||
|
||||
ObjectValues::value_type defaultValue(actualKey, nullRef);
|
||||
ObjectValues::value_type defaultValue(actualKey, null);
|
||||
it = value_.map_->insert(it, defaultValue);
|
||||
Value& value = (*it).second;
|
||||
return value;
|
||||
@@ -934,7 +992,7 @@ Value& Value::resolveReference(const char* key, bool isStatic) {
|
||||
|
||||
Value Value::get(ArrayIndex index, const Value& defaultValue) const {
|
||||
const Value* value = &((*this)[index]);
|
||||
return value == &nullRef ? defaultValue : *value;
|
||||
return value == &null ? defaultValue : *value;
|
||||
}
|
||||
|
||||
bool Value::isValidIndex(ArrayIndex index) const { return index < size(); }
|
||||
@@ -944,16 +1002,16 @@ const Value& Value::operator[](const char* key) const {
|
||||
type_ == nullValue || type_ == objectValue,
|
||||
"in Json::Value::operator[](char const*)const: requires objectValue");
|
||||
if (type_ == nullValue)
|
||||
return nullRef;
|
||||
return null;
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
CZString actualKey(key, CZString::noDuplication);
|
||||
ObjectValues::const_iterator it = value_.map_->find(actualKey);
|
||||
if (it == value_.map_->end())
|
||||
return nullRef;
|
||||
return null;
|
||||
return (*it).second;
|
||||
#else
|
||||
const Value* value = value_.map_->find(key);
|
||||
return value ? *value : nullRef;
|
||||
return value ? *value : null;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -983,81 +1041,42 @@ Value& Value::append(const Value& value) { return (*this)[size()] = value; }
|
||||
|
||||
Value Value::get(const char* key, const Value& defaultValue) const {
|
||||
const Value* value = &((*this)[key]);
|
||||
return value == &nullRef ? defaultValue : *value;
|
||||
return value == &null ? defaultValue : *value;
|
||||
}
|
||||
|
||||
Value Value::get(const std::string& key, const Value& defaultValue) const {
|
||||
return get(key.c_str(), defaultValue);
|
||||
}
|
||||
|
||||
|
||||
bool Value::removeMember(const char* key, Value* removed) {
|
||||
if (type_ != objectValue) {
|
||||
return false;
|
||||
}
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
CZString actualKey(key, CZString::noDuplication);
|
||||
ObjectValues::iterator it = value_.map_->find(actualKey);
|
||||
if (it == value_.map_->end())
|
||||
return false;
|
||||
*removed = it->second;
|
||||
value_.map_->erase(it);
|
||||
return true;
|
||||
#else
|
||||
Value* value = value_.map_->find(key);
|
||||
if (value) {
|
||||
*removed = *value;
|
||||
value_.map_.remove(key);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Value Value::removeMember(const char* key) {
|
||||
JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == objectValue,
|
||||
"in Json::Value::removeMember(): requires objectValue");
|
||||
if (type_ == nullValue)
|
||||
return nullRef;
|
||||
|
||||
Value removed; // null
|
||||
removeMember(key, &removed);
|
||||
return removed; // still null if removeMember() did nothing
|
||||
return null;
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
CZString actualKey(key, CZString::noDuplication);
|
||||
ObjectValues::iterator it = value_.map_->find(actualKey);
|
||||
if (it == value_.map_->end())
|
||||
return null;
|
||||
Value old(it->second);
|
||||
value_.map_->erase(it);
|
||||
return old;
|
||||
#else
|
||||
Value* value = value_.map_->find(key);
|
||||
if (value) {
|
||||
Value old(*value);
|
||||
value_.map_.remove(key);
|
||||
return old;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Value Value::removeMember(const std::string& key) {
|
||||
return removeMember(key.c_str());
|
||||
}
|
||||
|
||||
bool Value::removeIndex(ArrayIndex index, Value* removed) {
|
||||
if (type_ != arrayValue) {
|
||||
return false;
|
||||
}
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
JSON_FAIL_MESSAGE("removeIndex is not implemented for ValueInternalArray.");
|
||||
return false;
|
||||
#else
|
||||
CZString key(index);
|
||||
ObjectValues::iterator it = value_.map_->find(key);
|
||||
if (it == value_.map_->end()) {
|
||||
return false;
|
||||
}
|
||||
*removed = it->second;
|
||||
ArrayIndex oldSize = size();
|
||||
// shift left all items left, into the place of the "removed"
|
||||
for (ArrayIndex i = index; i < (oldSize - 1); ++i){
|
||||
CZString key(i);
|
||||
(*value_.map_)[key] = (*this)[i + 1];
|
||||
}
|
||||
// erase the last one ("leftover")
|
||||
CZString keyLast(oldSize - 1);
|
||||
ObjectValues::iterator itLast = value_.map_->find(keyLast);
|
||||
value_.map_->erase(itLast);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef JSON_USE_CPPTL
|
||||
Value Value::get(const CppTL::ConstString& key,
|
||||
const Value& defaultValue) const {
|
||||
@@ -1067,7 +1086,7 @@ Value Value::get(const CppTL::ConstString& key,
|
||||
|
||||
bool Value::isMember(const char* key) const {
|
||||
const Value* value = &((*this)[key]);
|
||||
return value != &nullRef;
|
||||
return value != &null;
|
||||
}
|
||||
|
||||
bool Value::isMember(const std::string& key) const {
|
||||
@@ -1226,22 +1245,14 @@ bool Value::isArray() const { return type_ == arrayValue; }
|
||||
|
||||
bool Value::isObject() const { return type_ == objectValue; }
|
||||
|
||||
void Value::setComment(const char* comment, size_t len, CommentPlacement placement) {
|
||||
void Value::setComment(const char* comment, CommentPlacement placement) {
|
||||
if (!comments_)
|
||||
comments_ = new CommentInfo[numberOfCommentPlacement];
|
||||
if ((len > 0) && (comment[len-1] == '\n')) {
|
||||
// Always discard trailing newline, to aid indentation.
|
||||
len -= 1;
|
||||
}
|
||||
comments_[placement].setComment(comment, len);
|
||||
}
|
||||
|
||||
void Value::setComment(const char* comment, CommentPlacement placement) {
|
||||
setComment(comment, strlen(comment), placement);
|
||||
comments_[placement].setComment(comment);
|
||||
}
|
||||
|
||||
void Value::setComment(const std::string& comment, CommentPlacement placement) {
|
||||
setComment(comment.c_str(), comment.length(), placement);
|
||||
setComment(comment.c_str(), placement);
|
||||
}
|
||||
|
||||
bool Value::hasComment(CommentPlacement placement) const {
|
||||
@@ -1254,6 +1265,14 @@ std::string Value::getComment(CommentPlacement placement) const {
|
||||
return "";
|
||||
}
|
||||
|
||||
void Value::setOffsetStart(size_t start) { start_ = start; }
|
||||
|
||||
void Value::setOffsetLimit(size_t limit) { limit_ = limit; }
|
||||
|
||||
size_t Value::getOffsetStart() const { return start_; }
|
||||
|
||||
size_t Value::getOffsetLimit() const { return limit_; }
|
||||
|
||||
std::string Value::toStyledString() const {
|
||||
StyledWriter writer;
|
||||
return writer.write(*this);
|
||||
@@ -1473,7 +1492,7 @@ const Value& Path::resolve(const Value& root) const {
|
||||
// Error: unable to resolve path (object value expected at position...)
|
||||
}
|
||||
node = &((*node)[arg.key_]);
|
||||
if (node == &Value::nullRef) {
|
||||
if (node == &Value::null) {
|
||||
// Error: unable to resolve path (object has no member named '' at
|
||||
// position...)
|
||||
}
|
||||
@@ -1494,7 +1513,7 @@ Value Path::resolve(const Value& root, const Value& defaultValue) const {
|
||||
if (!node->isObject())
|
||||
return defaultValue;
|
||||
node = &((*node)[arg.key_]);
|
||||
if (node == &Value::nullRef)
|
||||
if (node == &Value::null)
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
@@ -77,7 +77,7 @@ ValueIteratorBase::difference_type
|
||||
ValueIteratorBase::computeDistance(const SelfType& other) const {
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
#ifdef JSON_USE_CPPTL_SMALLMAP
|
||||
return other.current_ - current_;
|
||||
return current_ - other.current_;
|
||||
#else
|
||||
// Iterator for null value are initialized using the default
|
||||
// constructor, which initialize current_ to the default
|
||||
|
@@ -7,16 +7,13 @@
|
||||
#include <json/writer.h>
|
||||
#include "json_tool.h"
|
||||
#endif // if !defined(JSON_IS_AMALGAMATION)
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <math.h>
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below
|
||||
#include <float.h>
|
||||
@@ -29,19 +26,8 @@
|
||||
#pragma warning(disable : 4996)
|
||||
#endif
|
||||
|
||||
#if defined(__sun) && defined(__SVR4) //Solaris
|
||||
#include <ieeefp.h>
|
||||
#define isfinite finite
|
||||
#endif
|
||||
|
||||
namespace Json {
|
||||
|
||||
#if __cplusplus >= 201103L
|
||||
typedef std::unique_ptr<StreamWriter> StreamWriterPtr;
|
||||
#else
|
||||
typedef std::auto_ptr<StreamWriter> StreamWriterPtr;
|
||||
#endif
|
||||
|
||||
static bool containsControlCharacter(const char* str) {
|
||||
while (*str) {
|
||||
if (isControlCharacter(*(str++)))
|
||||
@@ -96,13 +82,13 @@ std::string valueToString(double value) {
|
||||
// visual studio 2005 to
|
||||
// avoid warning.
|
||||
#if defined(WINCE)
|
||||
len = _snprintf(buffer, sizeof(buffer), "%.17g", value);
|
||||
len = _snprintf(buffer, sizeof(buffer), "%.16g", value);
|
||||
#else
|
||||
len = sprintf_s(buffer, sizeof(buffer), "%.17g", value);
|
||||
len = sprintf_s(buffer, sizeof(buffer), "%.16g", value);
|
||||
#endif
|
||||
#else
|
||||
if (isfinite(value)) {
|
||||
len = snprintf(buffer, sizeof(buffer), "%.17g", value);
|
||||
len = snprintf(buffer, sizeof(buffer), "%.16g", value);
|
||||
} else {
|
||||
// IEEE standard states that NaN values will not compare to themselves
|
||||
if (value != value) {
|
||||
@@ -192,21 +178,28 @@ Writer::~Writer() {}
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
|
||||
FastWriter::FastWriter()
|
||||
: yamlCompatiblityEnabled_(false) {}
|
||||
: yamlCompatiblityEnabled_(false), dropNullPlaceholders_(false),
|
||||
omitEndingLineFeed_(false) {}
|
||||
|
||||
void FastWriter::enableYAMLCompatibility() { yamlCompatiblityEnabled_ = true; }
|
||||
|
||||
void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; }
|
||||
|
||||
void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; }
|
||||
|
||||
std::string FastWriter::write(const Value& root) {
|
||||
document_ = "";
|
||||
writeValue(root);
|
||||
document_ += "\n";
|
||||
if (!omitEndingLineFeed_)
|
||||
document_ += "\n";
|
||||
return document_;
|
||||
}
|
||||
|
||||
void FastWriter::writeValue(const Value& value) {
|
||||
switch (value.type()) {
|
||||
case nullValue:
|
||||
document_ += "null";
|
||||
if (!dropNullPlaceholders_)
|
||||
document_ += "null";
|
||||
break;
|
||||
case intValue:
|
||||
document_ += valueToString(value.asLargestInt());
|
||||
@@ -224,28 +217,28 @@ void FastWriter::writeValue(const Value& value) {
|
||||
document_ += valueToString(value.asBool());
|
||||
break;
|
||||
case arrayValue: {
|
||||
document_ += '[';
|
||||
document_ += "[";
|
||||
int size = value.size();
|
||||
for (int index = 0; index < size; ++index) {
|
||||
if (index > 0)
|
||||
document_ += ',';
|
||||
document_ += ",";
|
||||
writeValue(value[index]);
|
||||
}
|
||||
document_ += ']';
|
||||
document_ += "]";
|
||||
} break;
|
||||
case objectValue: {
|
||||
Value::Members members(value.getMemberNames());
|
||||
document_ += '{';
|
||||
document_ += "{";
|
||||
for (Value::Members::iterator it = members.begin(); it != members.end();
|
||||
++it) {
|
||||
const std::string& name = *it;
|
||||
if (it != members.begin())
|
||||
document_ += ',';
|
||||
document_ += ",";
|
||||
document_ += valueToQuotedString(name.c_str());
|
||||
document_ += yamlCompatiblityEnabled_ ? ": " : ":";
|
||||
writeValue(value[name]);
|
||||
}
|
||||
document_ += '}';
|
||||
document_ += "}";
|
||||
} break;
|
||||
}
|
||||
}
|
||||
@@ -309,7 +302,7 @@ void StyledWriter::writeValue(const Value& value) {
|
||||
writeCommentAfterValueOnSameLine(childValue);
|
||||
break;
|
||||
}
|
||||
document_ += ',';
|
||||
document_ += ",";
|
||||
writeCommentAfterValueOnSameLine(childValue);
|
||||
}
|
||||
unindent();
|
||||
@@ -343,7 +336,7 @@ void StyledWriter::writeArrayValue(const Value& value) {
|
||||
writeCommentAfterValueOnSameLine(childValue);
|
||||
break;
|
||||
}
|
||||
document_ += ',';
|
||||
document_ += ",";
|
||||
writeCommentAfterValueOnSameLine(childValue);
|
||||
}
|
||||
unindent();
|
||||
@@ -378,9 +371,6 @@ bool StyledWriter::isMultineArray(const Value& value) {
|
||||
addChildValues_ = true;
|
||||
int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
|
||||
for (int index = 0; index < size; ++index) {
|
||||
if (hasCommentForValue(value[index])) {
|
||||
isMultiLine = true;
|
||||
}
|
||||
writeValue(value[index]);
|
||||
lineLength += int(childValues_[index].length());
|
||||
}
|
||||
@@ -426,27 +416,26 @@ void StyledWriter::writeCommentBeforeValue(const Value& root) {
|
||||
|
||||
document_ += "\n";
|
||||
writeIndent();
|
||||
const std::string& comment = root.getComment(commentBefore);
|
||||
std::string::const_iterator iter = comment.begin();
|
||||
while (iter != comment.end()) {
|
||||
std::string normalizedComment = normalizeEOL(root.getComment(commentBefore));
|
||||
std::string::const_iterator iter = normalizedComment.begin();
|
||||
while (iter != normalizedComment.end()) {
|
||||
document_ += *iter;
|
||||
if (*iter == '\n' &&
|
||||
(iter != comment.end() && *(iter + 1) == '/'))
|
||||
if (*iter == '\n' && *(iter + 1) == '/')
|
||||
writeIndent();
|
||||
++iter;
|
||||
}
|
||||
|
||||
// Comments are stripped of trailing newlines, so add one here
|
||||
// Comments are stripped of newlines, so add one here
|
||||
document_ += "\n";
|
||||
}
|
||||
|
||||
void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) {
|
||||
if (root.hasComment(commentAfterOnSameLine))
|
||||
document_ += " " + root.getComment(commentAfterOnSameLine);
|
||||
document_ += " " + normalizeEOL(root.getComment(commentAfterOnSameLine));
|
||||
|
||||
if (root.hasComment(commentAfter)) {
|
||||
document_ += "\n";
|
||||
document_ += root.getComment(commentAfter);
|
||||
document_ += normalizeEOL(root.getComment(commentAfter));
|
||||
document_ += "\n";
|
||||
}
|
||||
}
|
||||
@@ -457,6 +446,25 @@ bool StyledWriter::hasCommentForValue(const Value& value) {
|
||||
value.hasComment(commentAfter);
|
||||
}
|
||||
|
||||
std::string StyledWriter::normalizeEOL(const std::string& text) {
|
||||
std::string normalized;
|
||||
normalized.reserve(text.length());
|
||||
const char* begin = text.c_str();
|
||||
const char* end = begin + text.length();
|
||||
const char* current = begin;
|
||||
while (current != end) {
|
||||
char c = *current++;
|
||||
if (c == '\r') // mac or dos EOL
|
||||
{
|
||||
if (*current == '\n') // convert dos EOL
|
||||
++current;
|
||||
normalized += '\n';
|
||||
} else // handle unix EOL & other char
|
||||
normalized += c;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
// Class StyledStreamWriter
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -468,10 +476,7 @@ void StyledStreamWriter::write(std::ostream& out, const Value& root) {
|
||||
document_ = &out;
|
||||
addChildValues_ = false;
|
||||
indentString_ = "";
|
||||
indented_ = true;
|
||||
writeCommentBeforeValue(root);
|
||||
if (!indented_) writeIndent();
|
||||
indented_ = true;
|
||||
writeValue(root);
|
||||
writeCommentAfterValueOnSameLine(root);
|
||||
*document_ << "\n";
|
||||
@@ -547,10 +552,8 @@ void StyledStreamWriter::writeArrayValue(const Value& value) {
|
||||
if (hasChildValue)
|
||||
writeWithIndent(childValues_[index]);
|
||||
else {
|
||||
if (!indented_) writeIndent();
|
||||
indented_ = true;
|
||||
writeIndent();
|
||||
writeValue(childValue);
|
||||
indented_ = false;
|
||||
}
|
||||
if (++index == size) {
|
||||
writeCommentAfterValueOnSameLine(childValue);
|
||||
@@ -591,9 +594,6 @@ bool StyledStreamWriter::isMultineArray(const Value& value) {
|
||||
addChildValues_ = true;
|
||||
int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
|
||||
for (int index = 0; index < size; ++index) {
|
||||
if (hasCommentForValue(value[index])) {
|
||||
isMultiLine = true;
|
||||
}
|
||||
writeValue(value[index]);
|
||||
lineLength += int(childValues_[index].length());
|
||||
}
|
||||
@@ -611,17 +611,24 @@ void StyledStreamWriter::pushValue(const std::string& value) {
|
||||
}
|
||||
|
||||
void StyledStreamWriter::writeIndent() {
|
||||
// blep intended this to look at the so-far-written string
|
||||
// to determine whether we are already indented, but
|
||||
// with a stream we cannot do that. So we rely on some saved state.
|
||||
// The caller checks indented_.
|
||||
/*
|
||||
Some comments in this method would have been nice. ;-)
|
||||
|
||||
if ( !document_.empty() )
|
||||
{
|
||||
char last = document_[document_.length()-1];
|
||||
if ( last == ' ' ) // already indented
|
||||
return;
|
||||
if ( last != '\n' ) // Comments may add new-line
|
||||
*document_ << '\n';
|
||||
}
|
||||
*/
|
||||
*document_ << '\n' << indentString_;
|
||||
}
|
||||
|
||||
void StyledStreamWriter::writeWithIndent(const std::string& value) {
|
||||
if (!indented_) writeIndent();
|
||||
writeIndent();
|
||||
*document_ << value;
|
||||
indented_ = false;
|
||||
}
|
||||
|
||||
void StyledStreamWriter::indent() { indentString_ += indentation_; }
|
||||
@@ -634,30 +641,19 @@ void StyledStreamWriter::unindent() {
|
||||
void StyledStreamWriter::writeCommentBeforeValue(const Value& root) {
|
||||
if (!root.hasComment(commentBefore))
|
||||
return;
|
||||
|
||||
if (!indented_) writeIndent();
|
||||
const std::string& comment = root.getComment(commentBefore);
|
||||
std::string::const_iterator iter = comment.begin();
|
||||
while (iter != comment.end()) {
|
||||
*document_ << *iter;
|
||||
if (*iter == '\n' &&
|
||||
(iter != comment.end() && *(iter + 1) == '/'))
|
||||
// writeIndent(); // would include newline
|
||||
*document_ << indentString_;
|
||||
++iter;
|
||||
}
|
||||
indented_ = false;
|
||||
*document_ << normalizeEOL(root.getComment(commentBefore));
|
||||
*document_ << "\n";
|
||||
}
|
||||
|
||||
void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) {
|
||||
if (root.hasComment(commentAfterOnSameLine))
|
||||
*document_ << ' ' << root.getComment(commentAfterOnSameLine);
|
||||
*document_ << " " + normalizeEOL(root.getComment(commentAfterOnSameLine));
|
||||
|
||||
if (root.hasComment(commentAfter)) {
|
||||
writeIndent();
|
||||
*document_ << root.getComment(commentAfter);
|
||||
*document_ << "\n";
|
||||
*document_ << normalizeEOL(root.getComment(commentAfter));
|
||||
*document_ << "\n";
|
||||
}
|
||||
indented_ = false;
|
||||
}
|
||||
|
||||
bool StyledStreamWriter::hasCommentForValue(const Value& value) {
|
||||
@@ -666,376 +662,28 @@ bool StyledStreamWriter::hasCommentForValue(const Value& value) {
|
||||
value.hasComment(commentAfter);
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// BuiltStyledStreamWriter
|
||||
|
||||
/// Scoped enums are not available until C++11.
|
||||
struct CommentStyle {
|
||||
/// Decide whether to write comments.
|
||||
enum Enum {
|
||||
None, ///< Drop all comments.
|
||||
Most, ///< Recover odd behavior of previous versions (not implemented yet).
|
||||
All ///< Keep all comments.
|
||||
};
|
||||
};
|
||||
|
||||
struct BuiltStyledStreamWriter : public StreamWriter
|
||||
{
|
||||
BuiltStyledStreamWriter(
|
||||
std::string const& indentation,
|
||||
CommentStyle::Enum cs,
|
||||
std::string const& colonSymbol,
|
||||
std::string const& nullSymbol,
|
||||
std::string const& endingLineFeedSymbol);
|
||||
virtual int write(Value const& root, std::ostream* sout);
|
||||
private:
|
||||
void writeValue(Value const& value);
|
||||
void writeArrayValue(Value const& value);
|
||||
bool isMultineArray(Value const& value);
|
||||
void pushValue(std::string const& value);
|
||||
void writeIndent();
|
||||
void writeWithIndent(std::string const& value);
|
||||
void indent();
|
||||
void unindent();
|
||||
void writeCommentBeforeValue(Value const& root);
|
||||
void writeCommentAfterValueOnSameLine(Value const& root);
|
||||
static bool hasCommentForValue(const Value& value);
|
||||
|
||||
typedef std::vector<std::string> ChildValues;
|
||||
|
||||
ChildValues childValues_;
|
||||
std::string indentString_;
|
||||
int rightMargin_;
|
||||
std::string indentation_;
|
||||
CommentStyle::Enum cs_;
|
||||
std::string colonSymbol_;
|
||||
std::string nullSymbol_;
|
||||
std::string endingLineFeedSymbol_;
|
||||
bool addChildValues_ : 1;
|
||||
bool indented_ : 1;
|
||||
};
|
||||
BuiltStyledStreamWriter::BuiltStyledStreamWriter(
|
||||
std::string const& indentation,
|
||||
CommentStyle::Enum cs,
|
||||
std::string const& colonSymbol,
|
||||
std::string const& nullSymbol,
|
||||
std::string const& endingLineFeedSymbol)
|
||||
: rightMargin_(74)
|
||||
, indentation_(indentation)
|
||||
, cs_(cs)
|
||||
, colonSymbol_(colonSymbol)
|
||||
, nullSymbol_(nullSymbol)
|
||||
, endingLineFeedSymbol_(endingLineFeedSymbol)
|
||||
, addChildValues_(false)
|
||||
, indented_(false)
|
||||
{
|
||||
}
|
||||
int BuiltStyledStreamWriter::write(Value const& root, std::ostream* sout)
|
||||
{
|
||||
sout_ = sout;
|
||||
addChildValues_ = false;
|
||||
indented_ = true;
|
||||
indentString_ = "";
|
||||
writeCommentBeforeValue(root);
|
||||
if (!indented_) writeIndent();
|
||||
indented_ = true;
|
||||
writeValue(root);
|
||||
writeCommentAfterValueOnSameLine(root);
|
||||
*sout_ << endingLineFeedSymbol_;
|
||||
sout_ = NULL;
|
||||
return 0;
|
||||
}
|
||||
void BuiltStyledStreamWriter::writeValue(Value const& value) {
|
||||
switch (value.type()) {
|
||||
case nullValue:
|
||||
pushValue(nullSymbol_);
|
||||
break;
|
||||
case intValue:
|
||||
pushValue(valueToString(value.asLargestInt()));
|
||||
break;
|
||||
case uintValue:
|
||||
pushValue(valueToString(value.asLargestUInt()));
|
||||
break;
|
||||
case realValue:
|
||||
pushValue(valueToString(value.asDouble()));
|
||||
break;
|
||||
case stringValue:
|
||||
pushValue(valueToQuotedString(value.asCString()));
|
||||
break;
|
||||
case booleanValue:
|
||||
pushValue(valueToString(value.asBool()));
|
||||
break;
|
||||
case arrayValue:
|
||||
writeArrayValue(value);
|
||||
break;
|
||||
case objectValue: {
|
||||
Value::Members members(value.getMemberNames());
|
||||
if (members.empty())
|
||||
pushValue("{}");
|
||||
else {
|
||||
writeWithIndent("{");
|
||||
indent();
|
||||
Value::Members::iterator it = members.begin();
|
||||
for (;;) {
|
||||
std::string const& name = *it;
|
||||
Value const& childValue = value[name];
|
||||
writeCommentBeforeValue(childValue);
|
||||
writeWithIndent(valueToQuotedString(name.c_str()));
|
||||
*sout_ << colonSymbol_;
|
||||
writeValue(childValue);
|
||||
if (++it == members.end()) {
|
||||
writeCommentAfterValueOnSameLine(childValue);
|
||||
break;
|
||||
}
|
||||
*sout_ << ",";
|
||||
writeCommentAfterValueOnSameLine(childValue);
|
||||
}
|
||||
unindent();
|
||||
writeWithIndent("}");
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void BuiltStyledStreamWriter::writeArrayValue(Value const& value) {
|
||||
unsigned size = value.size();
|
||||
if (size == 0)
|
||||
pushValue("[]");
|
||||
else {
|
||||
bool isMultiLine = (cs_ == CommentStyle::All) || isMultineArray(value);
|
||||
if (isMultiLine) {
|
||||
writeWithIndent("[");
|
||||
indent();
|
||||
bool hasChildValue = !childValues_.empty();
|
||||
unsigned index = 0;
|
||||
for (;;) {
|
||||
Value const& childValue = value[index];
|
||||
writeCommentBeforeValue(childValue);
|
||||
if (hasChildValue)
|
||||
writeWithIndent(childValues_[index]);
|
||||
else {
|
||||
if (!indented_) writeIndent();
|
||||
indented_ = true;
|
||||
writeValue(childValue);
|
||||
indented_ = false;
|
||||
}
|
||||
if (++index == size) {
|
||||
writeCommentAfterValueOnSameLine(childValue);
|
||||
break;
|
||||
}
|
||||
*sout_ << ",";
|
||||
writeCommentAfterValueOnSameLine(childValue);
|
||||
}
|
||||
unindent();
|
||||
writeWithIndent("]");
|
||||
} else // output on a single line
|
||||
std::string StyledStreamWriter::normalizeEOL(const std::string& text) {
|
||||
std::string normalized;
|
||||
normalized.reserve(text.length());
|
||||
const char* begin = text.c_str();
|
||||
const char* end = begin + text.length();
|
||||
const char* current = begin;
|
||||
while (current != end) {
|
||||
char c = *current++;
|
||||
if (c == '\r') // mac or dos EOL
|
||||
{
|
||||
assert(childValues_.size() == size);
|
||||
*sout_ << "[";
|
||||
if (!indentation_.empty()) *sout_ << " ";
|
||||
for (unsigned index = 0; index < size; ++index) {
|
||||
if (index > 0)
|
||||
*sout_ << ", ";
|
||||
*sout_ << childValues_[index];
|
||||
}
|
||||
if (!indentation_.empty()) *sout_ << " ";
|
||||
*sout_ << "]";
|
||||
}
|
||||
if (*current == '\n') // convert dos EOL
|
||||
++current;
|
||||
normalized += '\n';
|
||||
} else // handle unix EOL & other char
|
||||
normalized += c;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
bool BuiltStyledStreamWriter::isMultineArray(Value const& value) {
|
||||
int size = value.size();
|
||||
bool isMultiLine = size * 3 >= rightMargin_;
|
||||
childValues_.clear();
|
||||
for (int index = 0; index < size && !isMultiLine; ++index) {
|
||||
Value const& childValue = value[index];
|
||||
isMultiLine =
|
||||
isMultiLine || ((childValue.isArray() || childValue.isObject()) &&
|
||||
childValue.size() > 0);
|
||||
}
|
||||
if (!isMultiLine) // check if line length > max line length
|
||||
{
|
||||
childValues_.reserve(size);
|
||||
addChildValues_ = true;
|
||||
int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
|
||||
for (int index = 0; index < size; ++index) {
|
||||
if (hasCommentForValue(value[index])) {
|
||||
isMultiLine = true;
|
||||
}
|
||||
writeValue(value[index]);
|
||||
lineLength += int(childValues_[index].length());
|
||||
}
|
||||
addChildValues_ = false;
|
||||
isMultiLine = isMultiLine || lineLength >= rightMargin_;
|
||||
}
|
||||
return isMultiLine;
|
||||
}
|
||||
|
||||
void BuiltStyledStreamWriter::pushValue(std::string const& value) {
|
||||
if (addChildValues_)
|
||||
childValues_.push_back(value);
|
||||
else
|
||||
*sout_ << value;
|
||||
}
|
||||
|
||||
void BuiltStyledStreamWriter::writeIndent() {
|
||||
// blep intended this to look at the so-far-written string
|
||||
// to determine whether we are already indented, but
|
||||
// with a stream we cannot do that. So we rely on some saved state.
|
||||
// The caller checks indented_.
|
||||
|
||||
if (!indentation_.empty()) {
|
||||
// In this case, drop newlines too.
|
||||
*sout_ << '\n' << indentString_;
|
||||
}
|
||||
}
|
||||
|
||||
void BuiltStyledStreamWriter::writeWithIndent(std::string const& value) {
|
||||
if (!indented_) writeIndent();
|
||||
*sout_ << value;
|
||||
indented_ = false;
|
||||
}
|
||||
|
||||
void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; }
|
||||
|
||||
void BuiltStyledStreamWriter::unindent() {
|
||||
assert(indentString_.size() >= indentation_.size());
|
||||
indentString_.resize(indentString_.size() - indentation_.size());
|
||||
}
|
||||
|
||||
void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) {
|
||||
if (cs_ == CommentStyle::None) return;
|
||||
if (!root.hasComment(commentBefore))
|
||||
return;
|
||||
|
||||
if (!indented_) writeIndent();
|
||||
const std::string& comment = root.getComment(commentBefore);
|
||||
std::string::const_iterator iter = comment.begin();
|
||||
while (iter != comment.end()) {
|
||||
*sout_ << *iter;
|
||||
if (*iter == '\n' &&
|
||||
(iter != comment.end() && *(iter + 1) == '/'))
|
||||
// writeIndent(); // would write extra newline
|
||||
*sout_ << indentString_;
|
||||
++iter;
|
||||
}
|
||||
indented_ = false;
|
||||
}
|
||||
|
||||
void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) {
|
||||
if (cs_ == CommentStyle::None) return;
|
||||
if (root.hasComment(commentAfterOnSameLine))
|
||||
*sout_ << " " + root.getComment(commentAfterOnSameLine);
|
||||
|
||||
if (root.hasComment(commentAfter)) {
|
||||
writeIndent();
|
||||
*sout_ << root.getComment(commentAfter);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) {
|
||||
return value.hasComment(commentBefore) ||
|
||||
value.hasComment(commentAfterOnSameLine) ||
|
||||
value.hasComment(commentAfter);
|
||||
}
|
||||
|
||||
///////////////
|
||||
// StreamWriter
|
||||
|
||||
StreamWriter::StreamWriter()
|
||||
: sout_(NULL)
|
||||
{
|
||||
}
|
||||
StreamWriter::~StreamWriter()
|
||||
{
|
||||
}
|
||||
StreamWriter::Factory::~Factory()
|
||||
{}
|
||||
StreamWriterBuilder::StreamWriterBuilder()
|
||||
{
|
||||
setDefaults(&settings_);
|
||||
}
|
||||
StreamWriterBuilder::~StreamWriterBuilder()
|
||||
{}
|
||||
StreamWriter* StreamWriterBuilder::newStreamWriter() const
|
||||
{
|
||||
std::string indentation = settings_["indentation"].asString();
|
||||
std::string cs_str = settings_["commentStyle"].asString();
|
||||
bool eyc = settings_["enableYAMLCompatibility"].asBool();
|
||||
bool dnp = settings_["dropNullPlaceholders"].asBool();
|
||||
CommentStyle::Enum cs = CommentStyle::All;
|
||||
if (cs_str == "All") {
|
||||
cs = CommentStyle::All;
|
||||
} else if (cs_str == "None") {
|
||||
cs = CommentStyle::None;
|
||||
} else {
|
||||
throw std::runtime_error("commentStyle must be 'All' or 'None'");
|
||||
}
|
||||
std::string colonSymbol = " : ";
|
||||
if (eyc) {
|
||||
colonSymbol = ": ";
|
||||
} else if (indentation.empty()) {
|
||||
colonSymbol = ":";
|
||||
}
|
||||
std::string nullSymbol = "null";
|
||||
if (dnp) {
|
||||
nullSymbol = "";
|
||||
}
|
||||
std::string endingLineFeedSymbol = "";
|
||||
return new BuiltStyledStreamWriter(
|
||||
indentation, cs,
|
||||
colonSymbol, nullSymbol, endingLineFeedSymbol);
|
||||
}
|
||||
static void getValidWriterKeys(std::set<std::string>* valid_keys)
|
||||
{
|
||||
valid_keys->clear();
|
||||
valid_keys->insert("indentation");
|
||||
valid_keys->insert("commentStyle");
|
||||
valid_keys->insert("enableYAMLCompatibility");
|
||||
valid_keys->insert("dropNullPlaceholders");
|
||||
}
|
||||
bool StreamWriterBuilder::validate(Json::Value* invalid) const
|
||||
{
|
||||
Json::Value my_invalid;
|
||||
if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL
|
||||
Json::Value& inv = *invalid;
|
||||
bool valid = true;
|
||||
std::set<std::string> valid_keys;
|
||||
getValidWriterKeys(&valid_keys);
|
||||
Value::Members keys = settings_.getMemberNames();
|
||||
size_t n = keys.size();
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
std::string const& key = keys[i];
|
||||
if (valid_keys.find(key) == valid_keys.end()) {
|
||||
inv[key] = settings_[key];
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
// static
|
||||
void StreamWriterBuilder::setDefaults(Json::Value* settings)
|
||||
{
|
||||
//! [StreamWriterBuilderDefaults]
|
||||
(*settings)["commentStyle"] = "All";
|
||||
(*settings)["indentation"] = "\t";
|
||||
(*settings)["enableYAMLCompatibility"] = false;
|
||||
(*settings)["dropNullPlaceholders"] = false;
|
||||
//! [StreamWriterBuilderDefaults]
|
||||
}
|
||||
|
||||
std::string writeString(StreamWriter::Factory const& builder, Value const& root) {
|
||||
std::ostringstream sout;
|
||||
StreamWriterPtr const writer(builder.newStreamWriter());
|
||||
writer->write(root, &sout);
|
||||
return sout.str();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& sout, Value const& root) {
|
||||
StreamWriterBuilder builder;
|
||||
StreamWriterPtr const writer(builder.newStreamWriter());
|
||||
writer->write(root, &sout);
|
||||
std::ostream& operator<<(std::ostream& sout, const Value& root) {
|
||||
Json::StyledStreamWriter writer;
|
||||
writer.write(sout, root);
|
||||
return sout;
|
||||
}
|
||||
|
||||
|
@@ -9,15 +9,7 @@ ADD_EXECUTABLE( jsoncpp_test
|
||||
main.cpp
|
||||
)
|
||||
|
||||
|
||||
IF(JSONCPP_LIB_BUILD_SHARED)
|
||||
TARGET_LINK_LIBRARIES(jsoncpp_test jsoncpp_lib)
|
||||
ELSE(JSONCPP_LIB_BUILD_SHARED)
|
||||
TARGET_LINK_LIBRARIES(jsoncpp_test jsoncpp_lib_static)
|
||||
ENDIF(JSONCPP_LIB_BUILD_SHARED)
|
||||
|
||||
# another way to solve issue #90
|
||||
#set_target_properties(jsoncpp_test PROPERTIES COMPILE_FLAGS -ffloat-store)
|
||||
TARGET_LINK_LIBRARIES(jsoncpp_test jsoncpp_lib)
|
||||
|
||||
# Run unit tests in post-build
|
||||
# (default cmake workflow hides away the test result into a file, resulting in poor dev workflow?!?)
|
||||
|
@@ -323,7 +323,7 @@ void Runner::listTests() const {
|
||||
}
|
||||
|
||||
int Runner::runCommandLine(int argc, const char* argv[]) const {
|
||||
// typedef std::deque<std::string> TestNames;
|
||||
typedef std::deque<std::string> TestNames;
|
||||
Runner subrunner;
|
||||
for (int index = 1; index < argc; ++index) {
|
||||
std::string opt = argv[index];
|
||||
|
@@ -178,8 +178,8 @@ private:
|
||||
|
||||
template <typename T, typename U>
|
||||
TestResult& checkEqual(TestResult& result,
|
||||
T expected,
|
||||
U actual,
|
||||
const T& expected,
|
||||
const U& actual,
|
||||
const char* file,
|
||||
unsigned int line,
|
||||
const char* expr) {
|
||||
@@ -214,7 +214,7 @@ TestResult& checkStringEqual(TestResult& result,
|
||||
#define JSONTEST_ASSERT_PRED(expr) \
|
||||
{ \
|
||||
JsonTest::PredicateContext _minitest_Context = { \
|
||||
result_->predicateId_, __FILE__, __LINE__, #expr, NULL, NULL \
|
||||
result_->predicateId_, __FILE__, __LINE__, #expr \
|
||||
}; \
|
||||
result_->predicateStackTail_->next_ = &_minitest_Context; \
|
||||
result_->predicateId_ += 1; \
|
||||
|
@@ -7,7 +7,6 @@
|
||||
#include <json/config.h>
|
||||
#include <json/json.h>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
// Make numeric limits more convenient to talk about.
|
||||
// Assumes int type in 32 bits.
|
||||
@@ -18,8 +17,8 @@
|
||||
#define kint64min Json::Value::minInt64
|
||||
#define kuint64max Json::Value::maxUInt64
|
||||
|
||||
//static const double kdint64max = double(kint64max);
|
||||
//static const float kfint64max = float(kint64max);
|
||||
static const double kdint64max = double(kint64max);
|
||||
static const float kfint64max = float(kint64max);
|
||||
static const float kfint32max = float(kint32max);
|
||||
static const float kfuint32max = float(kuint32max);
|
||||
|
||||
@@ -199,18 +198,6 @@ JSONTEST_FIXTURE(ValueTest, objects) {
|
||||
|
||||
object1_["some other id"] = "foo";
|
||||
JSONTEST_ASSERT_EQUAL(Json::Value("foo"), object1_["some other id"]);
|
||||
JSONTEST_ASSERT_EQUAL(Json::Value("foo"), object1_["some other id"]);
|
||||
|
||||
// Remove.
|
||||
Json::Value got;
|
||||
bool did;
|
||||
did = object1_.removeMember("some other id", &got);
|
||||
JSONTEST_ASSERT_EQUAL(Json::Value("foo"), got);
|
||||
JSONTEST_ASSERT_EQUAL(true, did);
|
||||
got = Json::Value("bar");
|
||||
did = object1_.removeMember("some other id", &got);
|
||||
JSONTEST_ASSERT_EQUAL(Json::Value("bar"), got);
|
||||
JSONTEST_ASSERT_EQUAL(false, did);
|
||||
}
|
||||
|
||||
JSONTEST_FIXTURE(ValueTest, arrays) {
|
||||
@@ -253,10 +240,6 @@ JSONTEST_FIXTURE(ValueTest, arrays) {
|
||||
array1_[2] = Json::Value(17);
|
||||
JSONTEST_ASSERT_EQUAL(Json::Value(), array1_[1]);
|
||||
JSONTEST_ASSERT_EQUAL(Json::Value(17), array1_[2]);
|
||||
Json::Value got;
|
||||
JSONTEST_ASSERT_EQUAL(true, array1_.removeIndex(2, &got));
|
||||
JSONTEST_ASSERT_EQUAL(Json::Value(17), got);
|
||||
JSONTEST_ASSERT_EQUAL(false, array1_.removeIndex(2, &got)); // gone now
|
||||
}
|
||||
|
||||
JSONTEST_FIXTURE(ValueTest, null) {
|
||||
@@ -943,7 +926,7 @@ JSONTEST_FIXTURE(ValueTest, integers) {
|
||||
JSONTEST_ASSERT_EQUAL(float(uint64ToDouble(Json::UInt64(1) << 63)),
|
||||
val.asFloat());
|
||||
JSONTEST_ASSERT_EQUAL(true, val.asBool());
|
||||
JSONTEST_ASSERT_STRING_EQUAL("9.2233720368547758e+18",
|
||||
JSONTEST_ASSERT_STRING_EQUAL("9.223372036854776e+18",
|
||||
normalizeFloatingPointStr(val.asString()));
|
||||
|
||||
// int64 min
|
||||
@@ -991,7 +974,7 @@ JSONTEST_FIXTURE(ValueTest, integers) {
|
||||
JSONTEST_ASSERT_EQUAL(-9223372036854775808.0, val.asDouble());
|
||||
JSONTEST_ASSERT_EQUAL(-9223372036854775808.0, val.asFloat());
|
||||
JSONTEST_ASSERT_EQUAL(true, val.asBool());
|
||||
JSONTEST_ASSERT_STRING_EQUAL("-9.2233720368547758e+18",
|
||||
JSONTEST_ASSERT_STRING_EQUAL("-9.223372036854776e+18",
|
||||
normalizeFloatingPointStr(val.asString()));
|
||||
|
||||
// 10^19
|
||||
@@ -1082,7 +1065,7 @@ JSONTEST_FIXTURE(ValueTest, integers) {
|
||||
JSONTEST_ASSERT_EQUAL(18446744073709551616.0, val.asDouble());
|
||||
JSONTEST_ASSERT_EQUAL(18446744073709551616.0, val.asFloat());
|
||||
JSONTEST_ASSERT_EQUAL(true, val.asBool());
|
||||
JSONTEST_ASSERT_STRING_EQUAL("1.8446744073709552e+19",
|
||||
JSONTEST_ASSERT_STRING_EQUAL("1.844674407370955e+19",
|
||||
normalizeFloatingPointStr(val.asString()));
|
||||
#endif
|
||||
}
|
||||
@@ -1234,7 +1217,7 @@ JSONTEST_FIXTURE(ValueTest, nonIntegers) {
|
||||
normalizeFloatingPointStr(val.asString()));
|
||||
|
||||
val = Json::Value(1.2345678901234);
|
||||
JSONTEST_ASSERT_STRING_EQUAL("1.2345678901234001",
|
||||
JSONTEST_ASSERT_STRING_EQUAL("1.2345678901234",
|
||||
normalizeFloatingPointStr(val.asString()));
|
||||
|
||||
// A 16-digit floating point number.
|
||||
@@ -1497,15 +1480,34 @@ JSONTEST_FIXTURE(ValueTest, typeChecksThrowExceptions) {
|
||||
#endif
|
||||
}
|
||||
|
||||
struct StreamWriterTest : JsonTest::TestCase {};
|
||||
JSONTEST_FIXTURE(ValueTest, offsetAccessors) {
|
||||
Json::Value x;
|
||||
JSONTEST_ASSERT(x.getOffsetStart() == 0);
|
||||
JSONTEST_ASSERT(x.getOffsetLimit() == 0);
|
||||
x.setOffsetStart(10);
|
||||
x.setOffsetLimit(20);
|
||||
JSONTEST_ASSERT(x.getOffsetStart() == 10);
|
||||
JSONTEST_ASSERT(x.getOffsetLimit() == 20);
|
||||
Json::Value y(x);
|
||||
JSONTEST_ASSERT(y.getOffsetStart() == 10);
|
||||
JSONTEST_ASSERT(y.getOffsetLimit() == 20);
|
||||
Json::Value z;
|
||||
z.swap(y);
|
||||
JSONTEST_ASSERT(z.getOffsetStart() == 10);
|
||||
JSONTEST_ASSERT(z.getOffsetLimit() == 20);
|
||||
JSONTEST_ASSERT(y.getOffsetStart() == 0);
|
||||
JSONTEST_ASSERT(y.getOffsetLimit() == 0);
|
||||
}
|
||||
|
||||
JSONTEST_FIXTURE(StreamWriterTest, dropNullPlaceholders) {
|
||||
Json::StreamWriterBuilder b;
|
||||
struct WriterTest : JsonTest::TestCase {};
|
||||
|
||||
JSONTEST_FIXTURE(WriterTest, dropNullPlaceholders) {
|
||||
Json::FastWriter writer;
|
||||
Json::Value nullValue;
|
||||
b.settings_["dropNullPlaceholders"] = false;
|
||||
JSONTEST_ASSERT(Json::writeString(b, nullValue) == "null");
|
||||
b.settings_["dropNullPlaceholders"] = true;
|
||||
JSONTEST_ASSERT(Json::writeString(b, nullValue) == "");
|
||||
JSONTEST_ASSERT(writer.write(nullValue) == "null\n");
|
||||
|
||||
writer.dropNullPlaceholders();
|
||||
JSONTEST_ASSERT(writer.write(nullValue) == "\n");
|
||||
}
|
||||
|
||||
struct ReaderTest : JsonTest::TestCase {};
|
||||
@@ -1516,6 +1518,7 @@ JSONTEST_FIXTURE(ReaderTest, parseWithNoErrors) {
|
||||
bool ok = reader.parse("{ \"property\" : \"value\" }", root);
|
||||
JSONTEST_ASSERT(ok);
|
||||
JSONTEST_ASSERT(reader.getFormattedErrorMessages().size() == 0);
|
||||
JSONTEST_ASSERT(reader.getStructuredErrors().size() == 0);
|
||||
}
|
||||
|
||||
JSONTEST_FIXTURE(ReaderTest, parseWithNoErrorsTestingOffsets) {
|
||||
@@ -1527,6 +1530,25 @@ JSONTEST_FIXTURE(ReaderTest, parseWithNoErrorsTestingOffsets) {
|
||||
root);
|
||||
JSONTEST_ASSERT(ok);
|
||||
JSONTEST_ASSERT(reader.getFormattedErrorMessages().size() == 0);
|
||||
JSONTEST_ASSERT(reader.getStructuredErrors().size() == 0);
|
||||
JSONTEST_ASSERT(root["property"].getOffsetStart() == 15);
|
||||
JSONTEST_ASSERT(root["property"].getOffsetLimit() == 34);
|
||||
JSONTEST_ASSERT(root["property"][0].getOffsetStart() == 16);
|
||||
JSONTEST_ASSERT(root["property"][0].getOffsetLimit() == 23);
|
||||
JSONTEST_ASSERT(root["property"][1].getOffsetStart() == 25);
|
||||
JSONTEST_ASSERT(root["property"][1].getOffsetLimit() == 33);
|
||||
JSONTEST_ASSERT(root["obj"].getOffsetStart() == 44);
|
||||
JSONTEST_ASSERT(root["obj"].getOffsetLimit() == 76);
|
||||
JSONTEST_ASSERT(root["obj"]["nested"].getOffsetStart() == 57);
|
||||
JSONTEST_ASSERT(root["obj"]["nested"].getOffsetLimit() == 60);
|
||||
JSONTEST_ASSERT(root["obj"]["bool"].getOffsetStart() == 71);
|
||||
JSONTEST_ASSERT(root["obj"]["bool"].getOffsetLimit() == 75);
|
||||
JSONTEST_ASSERT(root["null"].getOffsetStart() == 87);
|
||||
JSONTEST_ASSERT(root["null"].getOffsetLimit() == 91);
|
||||
JSONTEST_ASSERT(root["false"].getOffsetStart() == 103);
|
||||
JSONTEST_ASSERT(root["false"].getOffsetLimit() == 108);
|
||||
JSONTEST_ASSERT(root.getOffsetStart() == 0);
|
||||
JSONTEST_ASSERT(root.getOffsetLimit() == 110);
|
||||
}
|
||||
|
||||
JSONTEST_FIXTURE(ReaderTest, parseWithOneError) {
|
||||
@@ -1537,6 +1559,13 @@ JSONTEST_FIXTURE(ReaderTest, parseWithOneError) {
|
||||
JSONTEST_ASSERT(reader.getFormattedErrorMessages() ==
|
||||
"* Line 1, Column 15\n Syntax error: value, object or array "
|
||||
"expected.\n");
|
||||
std::vector<Json::Reader::StructuredError> errors =
|
||||
reader.getStructuredErrors();
|
||||
JSONTEST_ASSERT(errors.size() == 1);
|
||||
JSONTEST_ASSERT(errors.at(0).offset_start == 14);
|
||||
JSONTEST_ASSERT(errors.at(0).offset_limit == 15);
|
||||
JSONTEST_ASSERT(errors.at(0).message ==
|
||||
"Syntax error: value, object or array expected.");
|
||||
}
|
||||
|
||||
JSONTEST_FIXTURE(ReaderTest, parseChineseWithOneError) {
|
||||
@@ -1547,6 +1576,13 @@ JSONTEST_FIXTURE(ReaderTest, parseChineseWithOneError) {
|
||||
JSONTEST_ASSERT(reader.getFormattedErrorMessages() ==
|
||||
"* Line 1, Column 19\n Syntax error: value, object or array "
|
||||
"expected.\n");
|
||||
std::vector<Json::Reader::StructuredError> errors =
|
||||
reader.getStructuredErrors();
|
||||
JSONTEST_ASSERT(errors.size() == 1);
|
||||
JSONTEST_ASSERT(errors.at(0).offset_start == 18);
|
||||
JSONTEST_ASSERT(errors.at(0).offset_limit == 19);
|
||||
JSONTEST_ASSERT(errors.at(0).message ==
|
||||
"Syntax error: value, object or array expected.");
|
||||
}
|
||||
|
||||
JSONTEST_FIXTURE(ReaderTest, parseWithDetailError) {
|
||||
@@ -1557,255 +1593,12 @@ JSONTEST_FIXTURE(ReaderTest, parseWithDetailError) {
|
||||
JSONTEST_ASSERT(reader.getFormattedErrorMessages() ==
|
||||
"* Line 1, Column 16\n Bad escape sequence in string\nSee "
|
||||
"Line 1, Column 20 for detail.\n");
|
||||
}
|
||||
|
||||
struct CharReaderTest : JsonTest::TestCase {};
|
||||
|
||||
JSONTEST_FIXTURE(CharReaderTest, parseWithNoErrors) {
|
||||
Json::CharReaderBuilder b;
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
Json::Value root;
|
||||
char const doc[] = "{ \"property\" : \"value\" }";
|
||||
bool ok = reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs);
|
||||
JSONTEST_ASSERT(ok);
|
||||
JSONTEST_ASSERT(errs.size() == 0);
|
||||
delete reader;
|
||||
}
|
||||
|
||||
JSONTEST_FIXTURE(CharReaderTest, parseWithNoErrorsTestingOffsets) {
|
||||
Json::CharReaderBuilder b;
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
Json::Value root;
|
||||
char const doc[] =
|
||||
"{ \"property\" : [\"value\", \"value2\"], \"obj\" : "
|
||||
"{ \"nested\" : 123, \"bool\" : true}, \"null\" : "
|
||||
"null, \"false\" : false }";
|
||||
bool ok = reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs);
|
||||
JSONTEST_ASSERT(ok);
|
||||
JSONTEST_ASSERT(errs.size() == 0);
|
||||
delete reader;
|
||||
}
|
||||
|
||||
JSONTEST_FIXTURE(CharReaderTest, parseWithOneError) {
|
||||
Json::CharReaderBuilder b;
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
Json::Value root;
|
||||
char const doc[] =
|
||||
"{ \"property\" :: \"value\" }";
|
||||
bool ok = reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs);
|
||||
JSONTEST_ASSERT(!ok);
|
||||
JSONTEST_ASSERT(errs ==
|
||||
"* Line 1, Column 15\n Syntax error: value, object or array "
|
||||
"expected.\n");
|
||||
delete reader;
|
||||
}
|
||||
|
||||
JSONTEST_FIXTURE(CharReaderTest, parseChineseWithOneError) {
|
||||
Json::CharReaderBuilder b;
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
Json::Value root;
|
||||
char const doc[] =
|
||||
"{ \"pr佐藤erty\" :: \"value\" }";
|
||||
bool ok = reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs);
|
||||
JSONTEST_ASSERT(!ok);
|
||||
JSONTEST_ASSERT(errs ==
|
||||
"* Line 1, Column 19\n Syntax error: value, object or array "
|
||||
"expected.\n");
|
||||
delete reader;
|
||||
}
|
||||
|
||||
JSONTEST_FIXTURE(CharReaderTest, parseWithDetailError) {
|
||||
Json::CharReaderBuilder b;
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
Json::Value root;
|
||||
char const doc[] =
|
||||
"{ \"property\" : \"v\\alue\" }";
|
||||
bool ok = reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs);
|
||||
JSONTEST_ASSERT(!ok);
|
||||
JSONTEST_ASSERT(errs ==
|
||||
"* Line 1, Column 16\n Bad escape sequence in string\nSee "
|
||||
"Line 1, Column 20 for detail.\n");
|
||||
delete reader;
|
||||
}
|
||||
|
||||
JSONTEST_FIXTURE(CharReaderTest, parseWithStackLimit) {
|
||||
Json::CharReaderBuilder b;
|
||||
Json::Value root;
|
||||
char const doc[] =
|
||||
"{ \"property\" : \"value\" }";
|
||||
{
|
||||
b.settings_["stackLimit"] = 2;
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
bool ok = reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs);
|
||||
JSONTEST_ASSERT(ok);
|
||||
JSONTEST_ASSERT(errs == "");
|
||||
JSONTEST_ASSERT_EQUAL("value", root["property"]);
|
||||
delete reader;
|
||||
}
|
||||
{
|
||||
b.settings_["stackLimit"] = 1;
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
JSONTEST_ASSERT_THROWS(reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs));
|
||||
delete reader;
|
||||
}
|
||||
}
|
||||
|
||||
struct CharReaderFailIfExtraTest : JsonTest::TestCase {};
|
||||
|
||||
JSONTEST_FIXTURE(CharReaderFailIfExtraTest, issue164) {
|
||||
// This is interpretted as a string value followed by a colon.
|
||||
Json::CharReaderBuilder b;
|
||||
Json::Value root;
|
||||
char const doc[] =
|
||||
" \"property\" : \"value\" }";
|
||||
{
|
||||
b.settings_["failIfExtra"] = false;
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
bool ok = reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs);
|
||||
JSONTEST_ASSERT(ok);
|
||||
JSONTEST_ASSERT(errs == "");
|
||||
JSONTEST_ASSERT_EQUAL("property", root);
|
||||
delete reader;
|
||||
}
|
||||
{
|
||||
b.settings_["failIfExtra"] = true;
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
bool ok = reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs);
|
||||
JSONTEST_ASSERT(!ok);
|
||||
JSONTEST_ASSERT_STRING_EQUAL(errs,
|
||||
"* Line 1, Column 13\n"
|
||||
" Extra non-whitespace after JSON value.\n");
|
||||
JSONTEST_ASSERT_EQUAL("property", root);
|
||||
delete reader;
|
||||
}
|
||||
{
|
||||
b.settings_["failIfExtra"] = false;
|
||||
b.strictMode(&b.settings_);
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
bool ok = reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs);
|
||||
JSONTEST_ASSERT(!ok);
|
||||
JSONTEST_ASSERT_STRING_EQUAL(errs,
|
||||
"* Line 1, Column 13\n"
|
||||
" Extra non-whitespace after JSON value.\n");
|
||||
JSONTEST_ASSERT_EQUAL("property", root);
|
||||
delete reader;
|
||||
}
|
||||
}
|
||||
JSONTEST_FIXTURE(CharReaderFailIfExtraTest, issue107) {
|
||||
// This is interpretted as an int value followed by a colon.
|
||||
Json::CharReaderBuilder b;
|
||||
Json::Value root;
|
||||
char const doc[] =
|
||||
"1:2:3";
|
||||
b.settings_["failIfExtra"] = true;
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
bool ok = reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs);
|
||||
JSONTEST_ASSERT(!ok);
|
||||
JSONTEST_ASSERT_STRING_EQUAL(
|
||||
"* Line 1, Column 2\n"
|
||||
" Extra non-whitespace after JSON value.\n",
|
||||
errs);
|
||||
JSONTEST_ASSERT_EQUAL(1, root.asInt());
|
||||
delete reader;
|
||||
}
|
||||
JSONTEST_FIXTURE(CharReaderFailIfExtraTest, commentAfterObject) {
|
||||
Json::CharReaderBuilder b;
|
||||
Json::Value root;
|
||||
{
|
||||
char const doc[] =
|
||||
"{ \"property\" : \"value\" } //trailing\n//comment\n";
|
||||
b.settings_["failIfExtra"] = true;
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
bool ok = reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs);
|
||||
JSONTEST_ASSERT(ok);
|
||||
JSONTEST_ASSERT_STRING_EQUAL("", errs);
|
||||
JSONTEST_ASSERT_EQUAL("value", root["property"]);
|
||||
delete reader;
|
||||
}
|
||||
}
|
||||
JSONTEST_FIXTURE(CharReaderFailIfExtraTest, commentAfterArray) {
|
||||
Json::CharReaderBuilder b;
|
||||
Json::Value root;
|
||||
char const doc[] =
|
||||
"[ \"property\" , \"value\" ] //trailing\n//comment\n";
|
||||
b.settings_["failIfExtra"] = true;
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
bool ok = reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs);
|
||||
JSONTEST_ASSERT(ok);
|
||||
JSONTEST_ASSERT_STRING_EQUAL("", errs);
|
||||
JSONTEST_ASSERT_EQUAL("value", root[1u]);
|
||||
delete reader;
|
||||
}
|
||||
JSONTEST_FIXTURE(CharReaderFailIfExtraTest, commentAfterBool) {
|
||||
Json::CharReaderBuilder b;
|
||||
Json::Value root;
|
||||
char const doc[] =
|
||||
" true /*trailing\ncomment*/";
|
||||
b.settings_["failIfExtra"] = true;
|
||||
Json::CharReader* reader(b.newCharReader());
|
||||
std::string errs;
|
||||
bool ok = reader->parse(
|
||||
doc, doc + std::strlen(doc),
|
||||
&root, &errs);
|
||||
JSONTEST_ASSERT(ok);
|
||||
JSONTEST_ASSERT_STRING_EQUAL("", errs);
|
||||
JSONTEST_ASSERT_EQUAL(true, root.asBool());
|
||||
delete reader;
|
||||
}
|
||||
|
||||
struct IteratorTest : JsonTest::TestCase {};
|
||||
|
||||
JSONTEST_FIXTURE(IteratorTest, distance) {
|
||||
Json::Value json;
|
||||
json["k1"] = "a";
|
||||
json["k2"] = "b";
|
||||
int dist;
|
||||
std::string str;
|
||||
for (Json::ValueIterator it = json.begin(); it != json.end(); ++it) {
|
||||
dist = it - json.begin();
|
||||
str = it->asString().c_str();
|
||||
}
|
||||
JSONTEST_ASSERT_EQUAL(1, dist);
|
||||
JSONTEST_ASSERT_STRING_EQUAL("b", str);
|
||||
std::vector<Json::Reader::StructuredError> errors =
|
||||
reader.getStructuredErrors();
|
||||
JSONTEST_ASSERT(errors.size() == 1);
|
||||
JSONTEST_ASSERT(errors.at(0).offset_start == 15);
|
||||
JSONTEST_ASSERT(errors.at(0).offset_limit == 23);
|
||||
JSONTEST_ASSERT(errors.at(0).message == "Bad escape sequence in string");
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
@@ -1828,10 +1621,9 @@ int main(int argc, const char* argv[]) {
|
||||
JSONTEST_REGISTER_FIXTURE(runner, ValueTest, compareArray);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, ValueTest, compareObject);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, ValueTest, compareType);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, ValueTest, offsetAccessors);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, ValueTest, typeChecksThrowExceptions);
|
||||
|
||||
JSONTEST_REGISTER_FIXTURE(runner, StreamWriterTest, dropNullPlaceholders);
|
||||
|
||||
JSONTEST_REGISTER_FIXTURE(runner, ReaderTest, parseWithNoErrors);
|
||||
JSONTEST_REGISTER_FIXTURE(
|
||||
runner, ReaderTest, parseWithNoErrorsTestingOffsets);
|
||||
@@ -1839,21 +1631,7 @@ int main(int argc, const char* argv[]) {
|
||||
JSONTEST_REGISTER_FIXTURE(runner, ReaderTest, parseChineseWithOneError);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, ReaderTest, parseWithDetailError);
|
||||
|
||||
JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithNoErrors);
|
||||
JSONTEST_REGISTER_FIXTURE(
|
||||
runner, CharReaderTest, parseWithNoErrorsTestingOffsets);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithOneError);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseChineseWithOneError);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithDetailError);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithStackLimit);
|
||||
|
||||
JSONTEST_REGISTER_FIXTURE(runner, CharReaderFailIfExtraTest, issue164);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, CharReaderFailIfExtraTest, issue107);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, CharReaderFailIfExtraTest, commentAfterObject);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, CharReaderFailIfExtraTest, commentAfterArray);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, CharReaderFailIfExtraTest, commentAfterBool);
|
||||
|
||||
JSONTEST_REGISTER_FIXTURE(runner, IteratorTest, distance);
|
||||
JSONTEST_REGISTER_FIXTURE(runner, WriterTest, dropNullPlaceholders);
|
||||
|
||||
return runner.runCommandLine(argc, argv);
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import os
|
||||
|
||||
paths = []
|
||||
for pattern in [ '*.actual', '*.actual-rewrite', '*.rewrite', '*.process-output' ]:
|
||||
paths += glob.glob('data/' + pattern)
|
||||
paths += glob.glob( 'data/' + pattern )
|
||||
|
||||
for path in paths:
|
||||
os.unlink(path)
|
||||
os.unlink( path )
|
||||
|
@@ -1,4 +0,0 @@
|
||||
// Comment for array
|
||||
.=[]
|
||||
// Comment within array
|
||||
.[0]="one-element"
|
@@ -1,5 +0,0 @@
|
||||
// Comment for array
|
||||
[
|
||||
// Comment within array
|
||||
"one-element"
|
||||
]
|
@@ -1,7 +1,5 @@
|
||||
.={}
|
||||
// Comment for array
|
||||
.test=[]
|
||||
// Comment within array
|
||||
.test[0]={}
|
||||
.test[0].a="aaa"
|
||||
.test[1]={}
|
||||
|
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"test":
|
||||
// Comment for array
|
||||
[
|
||||
// Comment within array
|
||||
{ "a" : "aaa" }, // Comment for a
|
||||
{ "b" : "bbb" }, // Comment for b
|
||||
{ "c" : "ccc" } // Comment for c
|
||||
|
@@ -11,13 +11,4 @@
|
||||
// Multiline comment cpp-style
|
||||
// Second line
|
||||
.cpp-test.c=3
|
||||
// Comment before double
|
||||
.cpp-test.d=4.1
|
||||
// Comment before string
|
||||
.cpp-test.e="e-string"
|
||||
// Comment before true
|
||||
.cpp-test.f=true
|
||||
// Comment before false
|
||||
.cpp-test.g=false
|
||||
// Comment before null
|
||||
.cpp-test.h=null
|
||||
.cpp-test.d=4
|
||||
|
@@ -12,15 +12,6 @@
|
||||
// Multiline comment cpp-style
|
||||
// Second line
|
||||
"c" : 3,
|
||||
// Comment before double
|
||||
"d" : 4.1,
|
||||
// Comment before string
|
||||
"e" : "e-string",
|
||||
// Comment before true
|
||||
"f" : true,
|
||||
// Comment before false
|
||||
"g" : false,
|
||||
// Comment before null
|
||||
"h" : null
|
||||
"d" : 4
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,11 @@
|
||||
from __future__ import print_function
|
||||
import glob
|
||||
import os.path
|
||||
for path in glob.glob('*.json'):
|
||||
for path in glob.glob( '*.json' ):
|
||||
text = file(path,'rt').read()
|
||||
target = os.path.splitext(path)[0] + '.expected'
|
||||
if os.path.exists(target):
|
||||
print('skipping:', target)
|
||||
if os.path.exists( target ):
|
||||
print 'skipping:', target
|
||||
else:
|
||||
print('creating:', target)
|
||||
print 'creating:', target
|
||||
file(target,'wt').write(text)
|
||||
|
||||
|
@@ -1,12 +1,12 @@
|
||||
# Simple implementation of a json test runner to run the test against json-py.
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import json
|
||||
import types
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: %s input-json-file", sys.argv[0])
|
||||
print "Usage: %s input-json-file", sys.argv[0]
|
||||
sys.exit(3)
|
||||
|
||||
input_path = sys.argv[1]
|
||||
@@ -15,50 +15,50 @@ actual_path = base_path + '.actual'
|
||||
rewrite_path = base_path + '.rewrite'
|
||||
rewrite_actual_path = base_path + '.actual-rewrite'
|
||||
|
||||
def valueTreeToString(fout, value, path = '.'):
|
||||
def valueTreeToString( fout, value, path = '.' ):
|
||||
ty = type(value)
|
||||
if ty is types.DictType:
|
||||
fout.write('%s={}\n' % path)
|
||||
fout.write( '%s={}\n' % path )
|
||||
suffix = path[-1] != '.' and '.' or ''
|
||||
names = value.keys()
|
||||
names.sort()
|
||||
for name in names:
|
||||
valueTreeToString(fout, value[name], path + suffix + name)
|
||||
valueTreeToString( fout, value[name], path + suffix + name )
|
||||
elif ty is types.ListType:
|
||||
fout.write('%s=[]\n' % path)
|
||||
for index, childValue in zip(xrange(0,len(value)), value):
|
||||
valueTreeToString(fout, childValue, path + '[%d]' % index)
|
||||
fout.write( '%s=[]\n' % path )
|
||||
for index, childValue in zip( xrange(0,len(value)), value ):
|
||||
valueTreeToString( fout, childValue, path + '[%d]' % index )
|
||||
elif ty is types.StringType:
|
||||
fout.write('%s="%s"\n' % (path,value))
|
||||
fout.write( '%s="%s"\n' % (path,value) )
|
||||
elif ty is types.IntType:
|
||||
fout.write('%s=%d\n' % (path,value))
|
||||
fout.write( '%s=%d\n' % (path,value) )
|
||||
elif ty is types.FloatType:
|
||||
fout.write('%s=%.16g\n' % (path,value))
|
||||
fout.write( '%s=%.16g\n' % (path,value) )
|
||||
elif value is True:
|
||||
fout.write('%s=true\n' % path)
|
||||
fout.write( '%s=true\n' % path )
|
||||
elif value is False:
|
||||
fout.write('%s=false\n' % path)
|
||||
fout.write( '%s=false\n' % path )
|
||||
elif value is None:
|
||||
fout.write('%s=null\n' % path)
|
||||
fout.write( '%s=null\n' % path )
|
||||
else:
|
||||
assert False and "Unexpected value type"
|
||||
|
||||
def parseAndSaveValueTree(input, actual_path):
|
||||
root = json.loads(input)
|
||||
fout = file(actual_path, 'wt')
|
||||
valueTreeToString(fout, root)
|
||||
def parseAndSaveValueTree( input, actual_path ):
|
||||
root = json.loads( input )
|
||||
fout = file( actual_path, 'wt' )
|
||||
valueTreeToString( fout, root )
|
||||
fout.close()
|
||||
return root
|
||||
|
||||
def rewriteValueTree(value, rewrite_path):
|
||||
rewrite = json.dumps(value)
|
||||
def rewriteValueTree( value, rewrite_path ):
|
||||
rewrite = json.dumps( value )
|
||||
#rewrite = rewrite[1:-1] # Somehow the string is quoted ! jsonpy bug ?
|
||||
file(rewrite_path, 'wt').write(rewrite + '\n')
|
||||
file( rewrite_path, 'wt').write( rewrite + '\n' )
|
||||
return rewrite
|
||||
|
||||
input = file(input_path, 'rt').read()
|
||||
root = parseAndSaveValueTree(input, actual_path)
|
||||
rewrite = rewriteValueTree(json.write(root), rewrite_path)
|
||||
rewrite_root = parseAndSaveValueTree(rewrite, rewrite_actual_path)
|
||||
input = file( input_path, 'rt' ).read()
|
||||
root = parseAndSaveValueTree( input, actual_path )
|
||||
rewrite = rewriteValueTree( json.write( root ), rewrite_path )
|
||||
rewrite_root = parseAndSaveValueTree( rewrite, rewrite_actual_path )
|
||||
|
||||
sys.exit(0)
|
||||
sys.exit( 0 )
|
||||
|
@@ -1,37 +1,17 @@
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from io import open
|
||||
from glob import glob
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
from glob import glob
|
||||
import optparse
|
||||
|
||||
VALGRIND_CMD = 'valgrind --tool=memcheck --leak-check=yes --undef-value-errors=yes '
|
||||
|
||||
def getStatusOutput(cmd):
|
||||
"""
|
||||
Return int, unicode (for both Python 2 and 3).
|
||||
Note: os.popen().close() would return None for 0.
|
||||
"""
|
||||
print(cmd, file=sys.stderr)
|
||||
pipe = os.popen(cmd)
|
||||
process_output = pipe.read()
|
||||
try:
|
||||
# We have been using os.popen(). When we read() the result
|
||||
# we get 'str' (bytes) in py2, and 'str' (unicode) in py3.
|
||||
# Ugh! There must be a better way to handle this.
|
||||
process_output = process_output.decode('utf-8')
|
||||
except AttributeError:
|
||||
pass # python3
|
||||
status = pipe.close()
|
||||
return status, process_output
|
||||
def compareOutputs(expected, actual, message):
|
||||
def compareOutputs( expected, actual, message ):
|
||||
expected = expected.strip().replace('\r','').split('\n')
|
||||
actual = actual.strip().replace('\r','').split('\n')
|
||||
diff_line = 0
|
||||
max_line_to_compare = min(len(expected), len(actual))
|
||||
for index in range(0,max_line_to_compare):
|
||||
max_line_to_compare = min( len(expected), len(actual) )
|
||||
for index in xrange(0,max_line_to_compare):
|
||||
if expected[index].strip() != actual[index].strip():
|
||||
diff_line = index + 1
|
||||
break
|
||||
@@ -39,7 +19,7 @@ def compareOutputs(expected, actual, message):
|
||||
diff_line = max_line_to_compare+1
|
||||
if diff_line == 0:
|
||||
return None
|
||||
def safeGetLine(lines, index):
|
||||
def safeGetLine( lines, index ):
|
||||
index += -1
|
||||
if index >= len(lines):
|
||||
return ''
|
||||
@@ -49,85 +29,85 @@ def compareOutputs(expected, actual, message):
|
||||
Actual: '%s'
|
||||
""" % (message, diff_line,
|
||||
safeGetLine(expected,diff_line),
|
||||
safeGetLine(actual,diff_line))
|
||||
safeGetLine(actual,diff_line) )
|
||||
|
||||
def safeReadFile(path):
|
||||
def safeReadFile( path ):
|
||||
try:
|
||||
return open(path, 'rt', encoding = 'utf-8').read()
|
||||
except IOError as e:
|
||||
return file( path, 'rt' ).read()
|
||||
except IOError, e:
|
||||
return '<File "%s" is missing: %s>' % (path,e)
|
||||
|
||||
def runAllTests(jsontest_executable_path, input_dir = None,
|
||||
use_valgrind=False, with_json_checker=False,
|
||||
writerClass='StyledWriter'):
|
||||
def runAllTests( jsontest_executable_path, input_dir = None,
|
||||
use_valgrind=False, with_json_checker=False ):
|
||||
if not input_dir:
|
||||
input_dir = os.path.join(os.getcwd(), 'data')
|
||||
tests = glob(os.path.join(input_dir, '*.json'))
|
||||
input_dir = os.path.join( os.getcwd(), 'data' )
|
||||
tests = glob( os.path.join( input_dir, '*.json' ) )
|
||||
if with_json_checker:
|
||||
test_jsonchecker = glob(os.path.join(input_dir, '../jsonchecker', '*.json'))
|
||||
test_jsonchecker = glob( os.path.join( input_dir, '../jsonchecker', '*.json' ) )
|
||||
else:
|
||||
test_jsonchecker = []
|
||||
failed_tests = []
|
||||
valgrind_path = use_valgrind and VALGRIND_CMD or ''
|
||||
for input_path in tests + test_jsonchecker:
|
||||
expect_failure = os.path.basename(input_path).startswith('fail')
|
||||
expect_failure = os.path.basename( input_path ).startswith( 'fail' )
|
||||
is_json_checker_test = (input_path in test_jsonchecker) or expect_failure
|
||||
print('TESTING:', input_path, end=' ')
|
||||
print 'TESTING:', input_path,
|
||||
options = is_json_checker_test and '--json-checker' or ''
|
||||
options += ' --json-writer %s'%writerClass
|
||||
cmd = '%s%s %s "%s"' % ( valgrind_path, jsontest_executable_path, options,
|
||||
input_path)
|
||||
status, process_output = getStatusOutput(cmd)
|
||||
pipe = os.popen( "%s%s %s %s" % (
|
||||
valgrind_path, jsontest_executable_path, options,
|
||||
input_path) )
|
||||
process_output = pipe.read()
|
||||
status = pipe.close()
|
||||
if is_json_checker_test:
|
||||
if expect_failure:
|
||||
if not status:
|
||||
print('FAILED')
|
||||
failed_tests.append((input_path, 'Parsing should have failed:\n%s' %
|
||||
safeReadFile(input_path)))
|
||||
if status is None:
|
||||
print 'FAILED'
|
||||
failed_tests.append( (input_path, 'Parsing should have failed:\n%s' %
|
||||
safeReadFile(input_path)) )
|
||||
else:
|
||||
print('OK')
|
||||
print 'OK'
|
||||
else:
|
||||
if status:
|
||||
print('FAILED')
|
||||
failed_tests.append((input_path, 'Parsing failed:\n' + process_output))
|
||||
if status is not None:
|
||||
print 'FAILED'
|
||||
failed_tests.append( (input_path, 'Parsing failed:\n' + process_output) )
|
||||
else:
|
||||
print('OK')
|
||||
print 'OK'
|
||||
else:
|
||||
base_path = os.path.splitext(input_path)[0]
|
||||
actual_output = safeReadFile(base_path + '.actual')
|
||||
actual_rewrite_output = safeReadFile(base_path + '.actual-rewrite')
|
||||
open(base_path + '.process-output', 'wt', encoding = 'utf-8').write(process_output)
|
||||
actual_output = safeReadFile( base_path + '.actual' )
|
||||
actual_rewrite_output = safeReadFile( base_path + '.actual-rewrite' )
|
||||
file(base_path + '.process-output','wt').write( process_output )
|
||||
if status:
|
||||
print('parsing failed')
|
||||
failed_tests.append((input_path, 'Parsing failed:\n' + process_output))
|
||||
print 'parsing failed'
|
||||
failed_tests.append( (input_path, 'Parsing failed:\n' + process_output) )
|
||||
else:
|
||||
expected_output_path = os.path.splitext(input_path)[0] + '.expected'
|
||||
expected_output = open(expected_output_path, 'rt', encoding = 'utf-8').read()
|
||||
detail = (compareOutputs(expected_output, actual_output, 'input')
|
||||
or compareOutputs(expected_output, actual_rewrite_output, 'rewrite'))
|
||||
expected_output = file( expected_output_path, 'rt' ).read()
|
||||
detail = ( compareOutputs( expected_output, actual_output, 'input' )
|
||||
or compareOutputs( expected_output, actual_rewrite_output, 'rewrite' ) )
|
||||
if detail:
|
||||
print('FAILED')
|
||||
failed_tests.append((input_path, detail))
|
||||
print 'FAILED'
|
||||
failed_tests.append( (input_path, detail) )
|
||||
else:
|
||||
print('OK')
|
||||
print 'OK'
|
||||
|
||||
if failed_tests:
|
||||
print()
|
||||
print('Failure details:')
|
||||
print
|
||||
print 'Failure details:'
|
||||
for failed_test in failed_tests:
|
||||
print('* Test', failed_test[0])
|
||||
print(failed_test[1])
|
||||
print()
|
||||
print('Test results: %d passed, %d failed.' % (len(tests)-len(failed_tests),
|
||||
len(failed_tests)))
|
||||
print '* Test', failed_test[0]
|
||||
print failed_test[1]
|
||||
print
|
||||
print 'Test results: %d passed, %d failed.' % (len(tests)-len(failed_tests),
|
||||
len(failed_tests) )
|
||||
return 1
|
||||
else:
|
||||
print('All %d tests passed.' % len(tests))
|
||||
print 'All %d tests passed.' % len(tests)
|
||||
return 0
|
||||
|
||||
def main():
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser(usage="%prog [options] <path to jsontestrunner.exe> [test case directory]")
|
||||
parser = OptionParser( usage="%prog [options] <path to jsontestrunner.exe> [test case directory]" )
|
||||
parser.add_option("--valgrind",
|
||||
action="store_true", dest="valgrind", default=False,
|
||||
help="run all the tests using valgrind to detect memory leaks")
|
||||
@@ -138,32 +118,17 @@ def main():
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if len(args) < 1 or len(args) > 2:
|
||||
parser.error('Must provides at least path to jsontestrunner executable.')
|
||||
sys.exit(1)
|
||||
parser.error( 'Must provides at least path to jsontestrunner executable.' )
|
||||
sys.exit( 1 )
|
||||
|
||||
jsontest_executable_path = os.path.normpath(os.path.abspath(args[0]))
|
||||
jsontest_executable_path = os.path.normpath( os.path.abspath( args[0] ) )
|
||||
if len(args) > 1:
|
||||
input_path = os.path.normpath(os.path.abspath(args[1]))
|
||||
input_path = os.path.normpath( os.path.abspath( args[1] ) )
|
||||
else:
|
||||
input_path = None
|
||||
status = runAllTests(jsontest_executable_path, input_path,
|
||||
use_valgrind=options.valgrind,
|
||||
with_json_checker=options.with_json_checker,
|
||||
writerClass='StyledWriter')
|
||||
if status:
|
||||
sys.exit(status)
|
||||
status = runAllTests(jsontest_executable_path, input_path,
|
||||
use_valgrind=options.valgrind,
|
||||
with_json_checker=options.with_json_checker,
|
||||
writerClass='StyledStreamWriter')
|
||||
if status:
|
||||
sys.exit(status)
|
||||
status = runAllTests(jsontest_executable_path, input_path,
|
||||
use_valgrind=options.valgrind,
|
||||
with_json_checker=options.with_json_checker,
|
||||
writerClass='BuiltStyledStreamWriter')
|
||||
if status:
|
||||
sys.exit(status)
|
||||
status = runAllTests( jsontest_executable_path, input_path,
|
||||
use_valgrind=options.valgrind, with_json_checker=options.with_json_checker )
|
||||
sys.exit( status )
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@@ -1,67 +1,61 @@
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from io import open
|
||||
from glob import glob
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import subprocess
|
||||
from glob import glob
|
||||
import optparse
|
||||
|
||||
VALGRIND_CMD = 'valgrind --tool=memcheck --leak-check=yes --undef-value-errors=yes'
|
||||
|
||||
class TestProxy(object):
|
||||
def __init__(self, test_exe_path, use_valgrind=False):
|
||||
self.test_exe_path = os.path.normpath(os.path.abspath(test_exe_path))
|
||||
def __init__( self, test_exe_path, use_valgrind=False ):
|
||||
self.test_exe_path = os.path.normpath( os.path.abspath( test_exe_path ) )
|
||||
self.use_valgrind = use_valgrind
|
||||
|
||||
def run(self, options):
|
||||
def run( self, options ):
|
||||
if self.use_valgrind:
|
||||
cmd = VALGRIND_CMD.split()
|
||||
else:
|
||||
cmd = []
|
||||
cmd.extend([self.test_exe_path, '--test-auto'] + options)
|
||||
try:
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
except:
|
||||
print(cmd)
|
||||
raise
|
||||
cmd.extend( [self.test_exe_path, '--test-auto'] + options )
|
||||
process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
|
||||
stdout = process.communicate()[0]
|
||||
if process.returncode:
|
||||
return False, stdout
|
||||
return True, stdout
|
||||
|
||||
def runAllTests(exe_path, use_valgrind=False):
|
||||
test_proxy = TestProxy(exe_path, use_valgrind=use_valgrind)
|
||||
status, test_names = test_proxy.run(['--list-tests'])
|
||||
def runAllTests( exe_path, use_valgrind=False ):
|
||||
test_proxy = TestProxy( exe_path, use_valgrind=use_valgrind )
|
||||
status, test_names = test_proxy.run( ['--list-tests'] )
|
||||
if not status:
|
||||
print("Failed to obtain unit tests list:\n" + test_names, file=sys.stderr)
|
||||
print >> sys.stderr, "Failed to obtain unit tests list:\n" + test_names
|
||||
return 1
|
||||
test_names = [name.strip() for name in test_names.decode('utf-8').strip().split('\n')]
|
||||
test_names = [name.strip() for name in test_names.strip().split('\n')]
|
||||
failures = []
|
||||
for name in test_names:
|
||||
print('TESTING %s:' % name, end=' ')
|
||||
succeed, result = test_proxy.run(['--test', name])
|
||||
print 'TESTING %s:' % name,
|
||||
succeed, result = test_proxy.run( ['--test', name] )
|
||||
if succeed:
|
||||
print('OK')
|
||||
print 'OK'
|
||||
else:
|
||||
failures.append((name, result))
|
||||
print('FAILED')
|
||||
failures.append( (name, result) )
|
||||
print 'FAILED'
|
||||
failed_count = len(failures)
|
||||
pass_count = len(test_names) - failed_count
|
||||
if failed_count:
|
||||
print()
|
||||
print
|
||||
for name, result in failures:
|
||||
print(result)
|
||||
print('%d/%d tests passed (%d failure(s))' % ( pass_count, len(test_names), failed_count))
|
||||
print result
|
||||
print '%d/%d tests passed (%d failure(s))' % (
|
||||
pass_count, len(test_names), failed_count)
|
||||
return 1
|
||||
else:
|
||||
print('All %d tests passed' % len(test_names))
|
||||
print 'All %d tests passed' % len(test_names)
|
||||
return 0
|
||||
|
||||
def main():
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser(usage="%prog [options] <path to test_lib_json.exe>")
|
||||
parser = OptionParser( usage="%prog [options] <path to test_lib_json.exe>" )
|
||||
parser.add_option("--valgrind",
|
||||
action="store_true", dest="valgrind", default=False,
|
||||
help="run all the tests using valgrind to detect memory leaks")
|
||||
@@ -69,11 +63,11 @@ def main():
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if len(args) != 1:
|
||||
parser.error('Must provides at least path to test_lib_json executable.')
|
||||
sys.exit(1)
|
||||
parser.error( 'Must provides at least path to test_lib_json executable.' )
|
||||
sys.exit( 1 )
|
||||
|
||||
exit_code = runAllTests(args[0], use_valgrind=options.valgrind)
|
||||
sys.exit(exit_code)
|
||||
exit_code = runAllTests( args[0], use_valgrind=options.valgrind )
|
||||
sys.exit( exit_code )
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
Reference in New Issue
Block a user