├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cog-batteries ├── cog_addheaders.py ├── cog_fluent.py ├── cog_getfilename.py ├── cog_optionswriter.py └── stringify.py ├── src ├── Jinja2CppLight.cpp ├── Jinja2CppLight.h ├── stringhelper.cpp └── stringhelper.h ├── test ├── gtest_supp.h ├── testJinja2CppLight.cpp └── teststringhelper.cpp └── thirdparty ├── cogapp ├── cog.py └── cogapp │ ├── __init__.py │ ├── __main__.py │ ├── backward.py │ ├── cogapp.py │ ├── makefiles.py │ ├── test_cogapp.py │ ├── test_makefiles.py │ ├── test_whiteutils.py │ └── whiteutils.py ├── gtest ├── gtest-all.cc ├── gtest │ └── gtest.h └── gtest_main.cc └── sources.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | *.pyc 7 | 8 | # Precompiled Headers 9 | *.gch 10 | *.pch 11 | 12 | # Compiled Dynamic libraries 13 | *.so 14 | *.dylib 15 | *.dll 16 | 17 | # Fortran module files 18 | *.mod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | 31 | build/ 32 | dist/ 33 | 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | matrix: 3 | include: 4 | - env: OSX=10.11 5 | os: osx 6 | osx_image: osx10.11 7 | rvm: system 8 | 9 | before_install: 10 | - if [ -f ".git/shallow" ]; then travis_retry git fetch --unshallow; fi 11 | 12 | script: 13 | - mkdir build 14 | - cd build 15 | - cmake .. -DCMAKE_INSTALL_PREFIX=../dist 16 | - make -j 4 17 | - make install 18 | - otool -L ../dist/bin/jinja2cpplight_unittests 19 | - otool -l ../dist/bin/jinja2cpplight_unittests | grep RPATH -A2 20 | - cd .. 21 | - dist/bin/jinja2cpplight_unittests 22 | 23 | notifications: 24 | email: 25 | on_success: never 26 | on_failure: never 27 | 28 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | if (NOT CMAKE_BUILD_TYPE) 4 | message("Setting build type to 'RelWithDebInfo'") 5 | set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) 6 | endif() 7 | 8 | set (LIB_BUILD_TYPE STATIC) 9 | if(UNIX) 10 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 11 | set (LIB_BUILD_TYPE SHARED) 12 | endif() 13 | 14 | # https://cmake.org/Wiki/CMake_RPATH_handling 15 | SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") 16 | SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) 17 | SET(CMAKE_MACOSX_RPATH TRUE) 18 | 19 | execute_process(COMMAND python -V ERROR_VARIABLE python_test) 20 | if(${python_test} MATCHES "Python 2\\.7\\..*") 21 | #message("Python 2.7 found") 22 | OPTION(PYTHON_AVAILABLE "Facilitates development, not needed for normal build." ON) 23 | else() 24 | OPTION(PYTHON_AVAILABLE "Facilitates development, not needed for normal build." OFF) 25 | endif() 26 | 27 | include_directories(.) 28 | include_directories(src) 29 | 30 | 31 | add_library(Jinja2CppLight ${LIB_BUILD_TYPE} src/Jinja2CppLight.cpp src/stringhelper.cpp) 32 | 33 | if(PYTHON_AVAILABLE) 34 | add_custom_target( 35 | cog 36 | python ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/cogapp/cog.py -q -I ${CMAKE_CURRENT_SOURCE_DIR}/cog-batteries -r ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/*.h 37 | ) 38 | add_dependencies(Jinja2CppLight cog) 39 | endif(PYTHON_AVAILABLE) 40 | 41 | if(UNIX) 42 | add_library(jinja2cpplight_gtest SHARED thirdparty/gtest/gtest-all.cc) 43 | target_link_libraries(jinja2cpplight_gtest pthread) 44 | else() 45 | add_library(jinja2cpplight_gtest thirdparty/gtest/gtest-all.cc) 46 | endif() 47 | target_include_directories(jinja2cpplight_gtest PRIVATE thirdparty/gtest) 48 | 49 | add_executable(jinja2cpplight_unittests thirdparty/gtest/gtest_main.cc test/testJinja2CppLight.cpp test/teststringhelper.cpp) 50 | target_link_libraries(jinja2cpplight_unittests jinja2cpplight_gtest) 51 | target_link_libraries(jinja2cpplight_unittests Jinja2CppLight) 52 | target_include_directories(jinja2cpplight_unittests PRIVATE thirdparty/gtest) 53 | 54 | INSTALL(TARGETS jinja2cpplight_gtest jinja2cpplight_unittests Jinja2CppLight 55 | LIBRARY DESTINATION lib 56 | ARCHIVE DESTINATION lib 57 | RUNTIME DESTINATION bin 58 | ) 59 | install(FILES src/Jinja2CppLight.h src/stringhelper.h DESTINATION include/Jinja2CppLight) 60 | 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. "Contributor Version" 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. "Covered Software" 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the terms of 34 | a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. "Larger Work" 41 | 42 | means a work that combines Covered Software with other material, in a 43 | separate file or files, that is not Covered Software. 44 | 45 | 1.8. "License" 46 | 47 | means this document. 48 | 49 | 1.9. "Licensable" 50 | 51 | means having the right to grant, to the maximum extent possible, whether 52 | at the time of the initial grant or subsequently, any and all of the 53 | rights conveyed by this License. 54 | 55 | 1.10. "Modifications" 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, 60 | deletion from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. "Patent Claims" of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, 67 | process, and apparatus claims, in any patent Licensable by such 68 | Contributor that would be infringed, but for the grant of the License, 69 | by the making, using, selling, offering for sale, having made, import, 70 | or transfer of either its Contributions or its Contributor Version. 71 | 72 | 1.12. "Secondary License" 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. "Source Code Form" 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. "You" (or "Your") 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, "You" includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, "control" means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or 104 | as part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its 108 | Contributions or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution 113 | become effective for each Contribution on the date the Contributor first 114 | distributes such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under 119 | this License. No additional rights or licenses will be implied from the 120 | distribution or licensing of Covered Software under this License. 121 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 122 | Contributor: 123 | 124 | a. for any code that a Contributor has removed from Covered Software; or 125 | 126 | b. for infringements caused by: (i) Your and any other third party's 127 | modifications of Covered Software, or (ii) the combination of its 128 | Contributions with other software (except as part of its Contributor 129 | Version); or 130 | 131 | c. under Patent Claims infringed by Covered Software in the absence of 132 | its Contributions. 133 | 134 | This License does not grant any rights in the trademarks, service marks, 135 | or logos of any Contributor (except as may be necessary to comply with 136 | the notice requirements in Section 3.4). 137 | 138 | 2.4. Subsequent Licenses 139 | 140 | No Contributor makes additional grants as a result of Your choice to 141 | distribute the Covered Software under a subsequent version of this 142 | License (see Section 10.2) or under the terms of a Secondary License (if 143 | permitted under the terms of Section 3.3). 144 | 145 | 2.5. Representation 146 | 147 | Each Contributor represents that the Contributor believes its 148 | Contributions are its original creation(s) or it has sufficient rights to 149 | grant the rights to its Contributions conveyed by this License. 150 | 151 | 2.6. Fair Use 152 | 153 | This License is not intended to limit any rights You have under 154 | applicable copyright doctrines of fair use, fair dealing, or other 155 | equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under 169 | the terms of this License. You must inform recipients that the Source 170 | Code Form of the Covered Software is governed by the terms of this 171 | License, and how they can obtain a copy of this License. You may not 172 | attempt to alter or restrict the recipients' rights in the Source Code 173 | Form. 174 | 175 | 3.2. Distribution of Executable Form 176 | 177 | If You distribute Covered Software in Executable Form then: 178 | 179 | a. such Covered Software must also be made available in Source Code Form, 180 | as described in Section 3.1, and You must inform recipients of the 181 | Executable Form how they can obtain a copy of such Source Code Form by 182 | reasonable means in a timely manner, at a charge no more than the cost 183 | of distribution to the recipient; and 184 | 185 | b. You may distribute such Executable Form under the terms of this 186 | License, or sublicense it under different terms, provided that the 187 | license for the Executable Form does not attempt to limit or alter the 188 | recipients' rights in the Source Code Form under this License. 189 | 190 | 3.3. Distribution of a Larger Work 191 | 192 | You may create and distribute a Larger Work under terms of Your choice, 193 | provided that You also comply with the requirements of this License for 194 | the Covered Software. If the Larger Work is a combination of Covered 195 | Software with a work governed by one or more Secondary Licenses, and the 196 | Covered Software is not Incompatible With Secondary Licenses, this 197 | License permits You to additionally distribute such Covered Software 198 | under the terms of such Secondary License(s), so that the recipient of 199 | the Larger Work may, at their option, further distribute the Covered 200 | Software under the terms of either this License or such Secondary 201 | License(s). 202 | 203 | 3.4. Notices 204 | 205 | You may not remove or alter the substance of any license notices 206 | (including copyright notices, patent notices, disclaimers of warranty, or 207 | limitations of liability) contained within the Source Code Form of the 208 | Covered Software, except that You may alter any license notices to the 209 | extent required to remedy known factual inaccuracies. 210 | 211 | 3.5. Application of Additional Terms 212 | 213 | You may choose to offer, and to charge a fee for, warranty, support, 214 | indemnity or liability obligations to one or more recipients of Covered 215 | Software. However, You may do so only on Your own behalf, and not on 216 | behalf of any Contributor. You must make it absolutely clear that any 217 | such warranty, support, indemnity, or liability obligation is offered by 218 | You alone, and You hereby agree to indemnify every Contributor for any 219 | liability incurred by such Contributor as a result of warranty, support, 220 | indemnity or liability terms You offer. You may include additional 221 | disclaimers of warranty and limitations of liability specific to any 222 | jurisdiction. 223 | 224 | 4. Inability to Comply Due to Statute or Regulation 225 | 226 | If it is impossible for You to comply with any of the terms of this License 227 | with respect to some or all of the Covered Software due to statute, 228 | judicial order, or regulation then You must: (a) comply with the terms of 229 | this License to the maximum extent possible; and (b) describe the 230 | limitations and the code they affect. Such description must be placed in a 231 | text file included with all distributions of the Covered Software under 232 | this License. Except to the extent prohibited by statute or regulation, 233 | such description must be sufficiently detailed for a recipient of ordinary 234 | skill to be able to understand it. 235 | 236 | 5. Termination 237 | 238 | 5.1. The rights granted under this License will terminate automatically if You 239 | fail to comply with any of its terms. However, if You become compliant, 240 | then the rights granted under this License from a particular Contributor 241 | are reinstated (a) provisionally, unless and until such Contributor 242 | explicitly and finally terminates Your grants, and (b) on an ongoing 243 | basis, if such Contributor fails to notify You of the non-compliance by 244 | some reasonable means prior to 60 days after You have come back into 245 | compliance. Moreover, Your grants from a particular Contributor are 246 | reinstated on an ongoing basis if such Contributor notifies You of the 247 | non-compliance by some reasonable means, this is the first time You have 248 | received notice of non-compliance with this License from such 249 | Contributor, and You become compliant prior to 30 days after Your receipt 250 | of the notice. 251 | 252 | 5.2. If You initiate litigation against any entity by asserting a patent 253 | infringement claim (excluding declaratory judgment actions, 254 | counter-claims, and cross-claims) alleging that a Contributor Version 255 | directly or indirectly infringes any patent, then the rights granted to 256 | You by any and all Contributors for the Covered Software under Section 257 | 2.1 of this License shall terminate. 258 | 259 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 260 | license agreements (excluding distributors and resellers) which have been 261 | validly granted by You or Your distributors under this License prior to 262 | termination shall survive termination. 263 | 264 | 6. Disclaimer of Warranty 265 | 266 | Covered Software is provided under this License on an "as is" basis, 267 | without warranty of any kind, either expressed, implied, or statutory, 268 | including, without limitation, warranties that the Covered Software is free 269 | of defects, merchantable, fit for a particular purpose or non-infringing. 270 | The entire risk as to the quality and performance of the Covered Software 271 | is with You. Should any Covered Software prove defective in any respect, 272 | You (not any Contributor) assume the cost of any necessary servicing, 273 | repair, or correction. This disclaimer of warranty constitutes an essential 274 | part of this License. No use of any Covered Software is authorized under 275 | this License except under this disclaimer. 276 | 277 | 7. Limitation of Liability 278 | 279 | Under no circumstances and under no legal theory, whether tort (including 280 | negligence), contract, or otherwise, shall any Contributor, or anyone who 281 | distributes Covered Software as permitted above, be liable to You for any 282 | direct, indirect, special, incidental, or consequential damages of any 283 | character including, without limitation, damages for lost profits, loss of 284 | goodwill, work stoppage, computer failure or malfunction, or any and all 285 | other commercial damages or losses, even if such party shall have been 286 | informed of the possibility of such damages. This limitation of liability 287 | shall not apply to liability for death or personal injury resulting from 288 | such party's negligence to the extent applicable law prohibits such 289 | limitation. Some jurisdictions do not allow the exclusion or limitation of 290 | incidental or consequential damages, so this exclusion and limitation may 291 | not apply to You. 292 | 293 | 8. Litigation 294 | 295 | Any litigation relating to this License may be brought only in the courts 296 | of a jurisdiction where the defendant maintains its principal place of 297 | business and such litigation shall be governed by laws of that 298 | jurisdiction, without reference to its conflict-of-law provisions. Nothing 299 | in this Section shall prevent a party's ability to bring cross-claims or 300 | counter-claims. 301 | 302 | 9. Miscellaneous 303 | 304 | This License represents the complete agreement concerning the subject 305 | matter hereof. If any provision of this License is held to be 306 | unenforceable, such provision shall be reformed only to the extent 307 | necessary to make it enforceable. Any law or regulation which provides that 308 | the language of a contract shall be construed against the drafter shall not 309 | be used to construe this License against a Contributor. 310 | 311 | 312 | 10. Versions of the License 313 | 314 | 10.1. New Versions 315 | 316 | Mozilla Foundation is the license steward. Except as provided in Section 317 | 10.3, no one other than the license steward has the right to modify or 318 | publish new versions of this License. Each version will be given a 319 | distinguishing version number. 320 | 321 | 10.2. Effect of New Versions 322 | 323 | You may distribute the Covered Software under the terms of the version 324 | of the License under which You originally received the Covered Software, 325 | or under the terms of any subsequent version published by the license 326 | steward. 327 | 328 | 10.3. Modified Versions 329 | 330 | If you create software not governed by this License, and you want to 331 | create a new license for such software, you may create and use a 332 | modified version of this License if you rename the license and remove 333 | any references to the name of the license steward (except to note that 334 | such modified license differs from this License). 335 | 336 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 337 | Licenses If You choose to distribute Source Code Form that is 338 | Incompatible With Secondary Licenses under the terms of this version of 339 | the License, the notice described in Exhibit B of this License must be 340 | attached. 341 | 342 | Exhibit A - Source Code Form License Notice 343 | 344 | This Source Code Form is subject to the 345 | terms of the Mozilla Public License, v. 346 | 2.0. If a copy of the MPL was not 347 | distributed with this file, You can 348 | obtain one at 349 | http://mozilla.org/MPL/2.0/. 350 | 351 | If it is not possible or desirable to put the notice in a particular file, 352 | then You may include the notice in a location (such as a LICENSE file in a 353 | relevant directory) where a recipient would be likely to look for such a 354 | notice. 355 | 356 | You may add additional accurate notices of copyright ownership. 357 | 358 | Exhibit B - "Incompatible With Secondary Licenses" Notice 359 | 360 | This Source Code Form is "Incompatible 361 | With Secondary Licenses", as defined by 362 | the Mozilla Public License, v. 2.0. 363 | 364 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Jinja2CppLight](#jinja2cpplight) 6 | - [How to use?](#how-to-use) 7 | - [overview](#overview) 8 | - [examples](#examples) 9 | - [Building](#building) 10 | - [Building on linux](#building-on-linux) 11 | - [Pre-requisites](#pre-requisites) 12 | - [Method](#method) 13 | - [Building on Windows](#building-on-windows) 14 | - [Pre-requisites:](#pre-requisites) 15 | - [Procedure](#procedure) 16 | - [Running unittests](#running-unittests) 17 | - [License](#license) 18 | 19 | 20 | 21 | # Jinja2CppLight 22 | (very) lightweight version of Jinja2 for C++ 23 | 24 | Lightweight templating engine for C++, based on Jinja2 25 | * no dependencies, everything you need to build is included 26 | * templates follow Jinja2 syntax 27 | * supports: 28 | * variable substitution 29 | * for loops 30 | * including nested for loops 31 | * if statements - partially: only if variable exists or not 32 | 33 | # How to use? 34 | 35 | ## overview 36 | 37 | * variable substitution: `{{somevar}}` will be replaced by the value of `somevar` 38 | * for loops: `{% for somevar in range(5) %}...{% endfor %}` will be expanded, assigning somevar the values of 39 | 0, 1, 2, 3 and 4, accessible as normal template variables, ie in this case `{{somevar}}` 40 | 41 | ## examples 42 | 43 | Simple example of using variable substitution: 44 | ```c++ 45 | Template mytemplate( R"d( 46 | This is my {{avalue}} template. It's {{secondvalue}}... 47 | Today's weather is {{weather}}. 48 | )d" ); 49 | mytemplate.setValue( "avalue", 3 ); 50 | mytemplate.setValue( "secondvalue", 12.123f ); 51 | mytemplate.setValue( "weather", "rain" ); 52 | string result = mytemplate.render(); 53 | cout << result << endl; 54 | string expectedResult = R"d( 55 | This is my 3 template. It's 12.123... 56 | Today's weather is rain. 57 | )d"; 58 | EXPECT_EQ( expectedResult, result ); 59 | ``` 60 | 61 | eg, example of using loops, eg to unroll some loops, maybe in an OpenCL kernel: 62 | ``` 63 | Template mytemplate( R"d( 64 | {% for i in range(its) %}a[{{i}}] = image[{{i}}]; 65 | {% for j in range(2) %}b[{{j}}] = image[{{j}}]; 66 | {% endfor %}{% endfor %} 67 | )d" ); 68 | mytemplate.setValue( "its", 3 ); 69 | string result = mytemplate.render(); 70 | string expectedResult = R"d( 71 | a[0] = image[0]; 72 | b[0] = image[0]; 73 | b[1] = image[1]; 74 | a[1] = image[1]; 75 | b[0] = image[0]; 76 | b[1] = image[1]; 77 | a[2] = image[2]; 78 | b[0] = image[0]; 79 | b[1] = image[1]; 80 | 81 | )d"; 82 | EXPECT_EQ( expectedResult, result ); 83 | ``` 84 | ``` 85 | string source = R"DELIM( 86 | {% for i in its %} 87 | a[{{i}}] = image[{{i}}]; 88 | {% endfor %} 89 | )DELIM"; 90 | 91 | Template mytemplate( source ); 92 | mytemplate.setValue( "its", TupleValue::create(0, 1.1, "2abc") ); 93 | string result = mytemplate.render(); 94 | cout << result << endl; 95 | string expectedResult = R"DELIM( 96 | 97 | a[0] = image[0]; 98 | 99 | a[1.1] = image[1.1]; 100 | 101 | a[2abc] = image[2abc]; 102 | 103 | )DELIM"; 104 | EXPECT_EQ( expectedResult, result ); 105 | ```` 106 | 107 | simple if condition: 108 | ``` 109 | const std::string source = "abc{% if its %}def{% endif %}ghi"; 110 | Template mytemplate(source); 111 | mytemplate.setValue("its", 3); 112 | const std::string result = mytemplate.render(); 113 | std::cout << "[" << result << "]" << endl; 114 | const std::string expectedResult = "abcdefghi"; 115 | EXPECT_EQ(expectedResult, result); 116 | ``` 117 | 118 | # Building 119 | 120 | ## Building on linux 121 | 122 | ### Pre-requisites 123 | 124 | * cmake 125 | * g++ 126 | * make 127 | 128 | ### Method 129 | 130 | ```bash 131 | git clone git@github.com:hughperkins/Jinja2CppLight.git 132 | cd Jinja2CppLight 133 | mkdir build 134 | cd build 135 | cmake .. 136 | make 137 | ``` 138 | 139 | ## Building on Windows 140 | 141 | ### Pre-requisites: 142 | 143 | * Visual Studio 2013 Community, or similar 144 | * cmake 145 | * git (eg msys-git) 146 | 147 | ### Procedure 148 | 149 | * use git to clone git@github.com:hughperkins/Jinja2CppLight.git 150 | * open cmake, and use it to generate visual studio project files, from the checked out repository 151 | * open visual studio, and build the generate visual studio project files, as `Release` 152 | 153 | # Running unittests 154 | 155 | After building, as above, on linux: 156 | ```bash 157 | ./jinja2cpplight_unittests 158 | ``` 159 | on Windows, from the Release directory folder: 160 | ``` 161 | jinja2cpplight_unittests 162 | ``` 163 | 164 | # Related projects 165 | 166 | For an alternative approach, using lua as a templating scripting language, see [luacpptemplater](https://github.com/hughperkins/luacpptemplater) 167 | 168 | # License 169 | 170 | Mozilla Public License 171 | 172 | -------------------------------------------------------------------------------- /cog-batteries/cog_addheaders.py: -------------------------------------------------------------------------------- 1 | # Copyright Hugh Perkins 2014,2015 hughperkins at gmail 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public License, 4 | # v. 2.0. If a copy of the MPL was not distributed with this file, You can 5 | # obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | # simply import this module in your header file like: 8 | # 9 | # // [[[cog 10 | # // import cog_addheaders 11 | # // cog_addheaders.add() 12 | # // ]]] 13 | # // [[[end]]] 14 | # 15 | # ... and run cog on the header file, to generate the header declarations 16 | 17 | import cog 18 | 19 | def add( classname = ''): 20 | # debug = open('debug.txt', 'a' ) 21 | # debug.write( 'foo\n') 22 | # debug.write( 'infile [' + cog.inFile + ']\n' ) 23 | 24 | infile = cog.inFile 25 | splitinfile = infile.replace('\\','/').split('/') 26 | infilename = splitinfile[ len(splitinfile) - 1 ] 27 | if classname == '': 28 | classname = infilename.replace('.h','') 29 | cppfile = infile.replace('.h','.cpp') 30 | # cog.outl( '// classname: ' + classname ) 31 | # cog.outl( '// cppfile: ' + infilename.replace('.h','.cpp' ) ) 32 | f = open( cppfile, 'r') 33 | in_multiline_comment = False 34 | in_header = False; 35 | line = f.readline() 36 | cog.outl( '// generated, using cog:' ) 37 | while( line != '' ): 38 | # cog.outl(line) 39 | if( line.strip().find("/*") >= 0 ): 40 | in_multiline_comment = True 41 | if( line.strip().find("*/") >= 0 ): 42 | in_multiline_comment = False 43 | if not in_multiline_comment: 44 | if( in_header or line.find( classname + '::' ) >= 0 and line.find("(") >= 0 and line.strip().find("//") != 0 ) and line.find( ";" ) < 0: 45 | in_header = True 46 | fnheader = line.replace( classname + '::', '' ) 47 | fnheader = fnheader.replace( '{', '' ) 48 | fnheader = fnheader.replace( ') :', ')' ) 49 | if fnheader.find(")") >= 0: 50 | in_header = False 51 | fnheader = fnheader.strip().replace( ')', ');' ) 52 | fnheader = fnheader.strip().replace( ';const', 'const;' ) 53 | fnheader = fnheader.strip().replace( '; const', ' const;' ) 54 | cog.outl( fnheader ); 55 | line = f.readline() 56 | f.close() 57 | cog.outl('') 58 | 59 | # debug.close() 60 | 61 | def add_templated(): 62 | # debug = open('debug.txt', 'a' ) 63 | # debug.write( 'foo\n') 64 | # debug.write( 'infile [' + cog.inFile + ']\n' ) 65 | 66 | infile = cog.inFile 67 | cppfile = infile.replace('.h','.cpp') 68 | splitinfile = infile.replace('\\','/').split('/') 69 | infilename = splitinfile[ len(splitinfile) - 1 ] 70 | classname = infilename.replace('.h','') 71 | # cog.outl( '// classname: ' + classname ) 72 | # cog.outl( '// cppfile: ' + infilename.replace('.h','.cpp' ) ) 73 | f = open( cppfile, 'r') 74 | in_multiline_comment = False 75 | in_header = False; 76 | line = f.readline() 77 | cog.outl( '// generated, using cog:' ) 78 | while( line != '' ): 79 | # cog.outl(line) 80 | if( line.strip().find("/*") >= 0 ): 81 | in_multiline_comment = True 82 | if( line.strip().find("*/") >= 0 ): 83 | in_multiline_comment = False 84 | if not in_multiline_comment: 85 | if( in_header or line.find( classname + '::' ) >= 0 and line.find("(") >= 0 and line.strip().find("//") != 0 ) and line.find( ";" ) < 0: 86 | in_header = True 87 | fnheader = line.replace('template< typename T >', '' ).replace( classname + '::', '' ) 88 | fnheader = fnheader.replace( '{', '' ) 89 | fnheader = fnheader.replace( ') :', ')' ) 90 | if fnheader.find(")") >= 0: 91 | in_header = False 92 | fnheader = fnheader.strip().replace( ')', ');' ) 93 | fnheader = fnheader.strip().replace( ';const', 'const;' ) 94 | fnheader = fnheader.strip().replace( '; const', ' const;' ) 95 | cog.outl( fnheader ); 96 | line = f.readline() 97 | f.close() 98 | cog.outl('') 99 | 100 | 101 | -------------------------------------------------------------------------------- /cog-batteries/cog_fluent.py: -------------------------------------------------------------------------------- 1 | # Copyright Hugh Perkins 2014,2015 hughperkins at gmail 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public License, 4 | # v. 2.0. If a copy of the MPL was not distributed with this file, You can 5 | # obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | import cog 8 | 9 | def go(classname, ints = [], floats = []): 10 | cog.outl( '// generated, using cog:' ) 11 | for thisint in ints: 12 | cog.outl('int ' + thisint + ' = 0;') 13 | for thisfloat in floats: 14 | cog.outl('float ' + thisfloat + ' = 0;') 15 | for thisint in ints: 16 | thisintTitlecase = thisint[0].upper() + thisint[1:] 17 | cog.outl(classname + ' ' + thisintTitlecase + '( int ' + '_' + thisint + ' ) {') 18 | cog.outl(' this->' + thisint + ' = _' + thisint + ';') 19 | cog.outl(' return *this;') 20 | cog.outl('}') 21 | for thisfloat in floats: 22 | thisfloatTitlecase = thisfloat[0].upper() + thisfloat[1:] 23 | cog.outl(classname + ' ' + thisfloatTitlecase + '( float ' + '_' + thisfloat + ' ) {') 24 | cog.outl(' this->' + thisfloat + ' = _' + thisfloat + ';') 25 | cog.outl(' return *this;') 26 | cog.outl('}') 27 | 28 | def gov2(classname, ints = [], floats = []): 29 | cog.outl( '// generated, using cog:' ) 30 | for thisint in ints: 31 | cog.outl('int _' + thisint + ' = 0;') 32 | for thisfloat in floats: 33 | cog.outl('float _' + thisfloat + ' = 0;') 34 | for thisint in ints: 35 | thisintTitlecase = thisint[0].upper() + thisint[1:] 36 | cog.outl(classname + ' ' + thisint + '( int ' + '_' + thisint + ' ) {') 37 | cog.outl(' this->_' + thisint + ' = _' + thisint + ';') 38 | cog.outl(' return *this;') 39 | cog.outl('}') 40 | for thisfloat in floats: 41 | thisfloatTitlecase = thisfloat[0].upper() + thisfloat[1:] 42 | cog.outl(classname + ' ' + thisfloat + '( float ' + '_' + thisfloat + ' ) {') 43 | cog.outl(' this->_' + thisfloat + ' = _' + thisfloat + ';') 44 | cog.outl(' return *this;') 45 | cog.outl('}') 46 | 47 | -------------------------------------------------------------------------------- /cog-batteries/cog_getfilename.py: -------------------------------------------------------------------------------- 1 | # Copyright Hugh Perkins 2014,2015 hughperkins at gmail 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public License, 4 | # v. 2.0. If a copy of the MPL was not distributed with this file, You can 5 | # obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | import cog 8 | import os.path 9 | 10 | def get_file_name(): 11 | infile = cog.inFile 12 | cppfile = infile.replace('.h','.cpp') 13 | splitinfile = infile.replace('\\','/').split('/') 14 | infilename = splitinfile[ len(splitinfile) - 1 ] 15 | return classname 16 | 17 | def get_class_name(): 18 | infile = cog.inFile 19 | cppfile = infile.replace('.h','.cpp') 20 | splitinfile = infile.replace('\\','/').split('/') 21 | infilename = splitinfile[ len(splitinfile) - 1 ] 22 | ( classname, _ ) = os.path.splitext( infilename ) 23 | return classname 24 | 25 | -------------------------------------------------------------------------------- /cog-batteries/cog_optionswriter.py: -------------------------------------------------------------------------------- 1 | # Copyright Hugh Perkins 2014,2015 hughperkins at gmail 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public License, 4 | # v. 2.0. If a copy of the MPL was not distributed with this file, You can 5 | # obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | import cog 8 | 9 | def write_options( optionsList ): 10 | cog.outl( '// generated, using cog:' ) 11 | for option in optionsList: 12 | optionTcase = option[0].upper() + option[1:] 13 | gOption = 'g' + optionTcase 14 | cog.outl( 'options += " -D' + gOption + '=" + toString( ' + option + ' );' ) 15 | 16 | -------------------------------------------------------------------------------- /cog-batteries/stringify.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | import cog 3 | 4 | def write_kernel( var_name, kernel_filename ): 5 | cog.outl( 'string kernelFilename = "' + kernel_filename + '";' ) 6 | f = open( kernel_filename, 'r') 7 | line = f.readline() 8 | cog.outl( 'const char * ' + var_name + ' = ' ) 9 | while( line != '' ): 10 | cog.outl( '"' + line.strip().replace('\\','\\\\') + '\\n" ' ) 11 | line = f.readline() 12 | cog.outl( '"";') 13 | f.close() 14 | 15 | def write_file2( filepath ): 16 | f = open( filepath, 'r') 17 | line = f.readline() 18 | while( line != '' ): 19 | line = process_includes( line ) 20 | cog.outl( '"' + line.rstrip().replace('\\','\\\\').replace('"', '\\"') + '\\n" ' ) 21 | line = f.readline() 22 | f.close() 23 | 24 | def process_includes( line ): 25 | if line.strip().find('#include') != 0: 26 | return line 27 | line = line.replace('<','"').replace('>','"') # standardize quotes a bit... 28 | targetpath = line.split('"')[1] 29 | line = '' 30 | cog.outl('// including ' + targetpath + ':') 31 | write_file2( '../' + targetpath ) 32 | return line 33 | 34 | def write_kernel2( kernelVarName, kernel_filename, kernelName, options ): 35 | # cog.outl( 'string kernelFilename = "' + kernel_filename + '";' ) 36 | cog.outl( '// generated using cog:' ) 37 | cog.outl( 'const char * ' + kernelVarName + 'Source = ' ) 38 | write_file2( '../' + kernel_filename ) 39 | cog.outl( '"";') 40 | cog.outl( kernelVarName + ' = cl->buildKernelFromString( ' + kernelVarName + 'Source, "' + kernelName + '", ' + options + ', "' + kernel_filename + '" );' ) 41 | 42 | def write_kernel3( kernelVarName, kernel_filename, kernelName, options ): 43 | # cog.outl( 'string kernelFilename = "' + kernel_filename + '";' ) 44 | cog.outl( '// generated using cog:' ) 45 | f = open( '../' + kernel_filename, 'r') 46 | line = f.readline() 47 | cog.outl( 'const char * ' + kernelVarName + 'Source = R"DELIM(\n' ) 48 | while( line != '' ): 49 | cog.outl( '' + line.rstrip() ) 50 | line = f.readline() 51 | cog.outl( ')DELIM";') 52 | f.close() 53 | cog.outl( kernelVarName + ' = cl->buildKernelFromString( ' + kernelVarName + 'Source, "' + kernelName + '", ' + options + ', "' + kernel_filename + '" );' ) 54 | 55 | -------------------------------------------------------------------------------- /src/Jinja2CppLight.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Hugh Perkins 2015 hughperkins at gmail 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public License, 4 | // v. 2.0. If a copy of the MPL was not distributed with this file, You can 5 | // obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "stringhelper.h" 14 | 15 | #include "Jinja2CppLight.h" 16 | 17 | using namespace std; 18 | 19 | namespace 20 | { 21 | const std::string JINJA2_TRUE = "True"; 22 | const std::string JINJA2_FALSE = "False"; 23 | const std::string JINJA2_NOT = "not"; 24 | } 25 | 26 | namespace Jinja2CppLight { 27 | 28 | #undef VIRTUAL 29 | #define VIRTUAL 30 | #undef STATIC 31 | #define STATIC 32 | 33 | Template::Template( std::string sourceCode ) : 34 | sourceCode( sourceCode ) 35 | {} 36 | 37 | STATIC bool Template::isNumber( std::string astring, int *p_value ) { 38 | istringstream in( astring ); 39 | int value; 40 | if( in >> value && in.eof() ) { 41 | *p_value = value; 42 | return true; 43 | } 44 | return false; 45 | } 46 | VIRTUAL Template::~Template() { 47 | valueByName.clear(); 48 | } 49 | Template &Template::setValue( std::string name, int value ) { 50 | valueByName[ name ] = std::make_shared( value ); 51 | return *this; 52 | } 53 | Template &Template::setValue( std::string name, float value ) { 54 | valueByName[ name ] = std::make_shared( value ); 55 | return *this; 56 | } 57 | Template &Template::setValue( std::string name, std::string value ) { 58 | valueByName[ name ] = std::make_shared( std::move(value) ); 59 | return *this; 60 | } 61 | 62 | Template&Template::setValue( std::string name, TupleValue value) { 63 | valueByName[ name ] = std::make_shared( std::move(value) ); 64 | return *this; 65 | 66 | } 67 | std::string Template::render() { 68 | std::unique_ptr root(new Root()); 69 | size_t finalPos = eatSection(0, root.get() ); 70 | if( finalPos != sourceCode.length() ) { 71 | throw render_error("some sourcecode found at end: " + sourceCode.substr( finalPos ) ); 72 | } 73 | return root->render(valueByName); 74 | } 75 | 76 | void Template::print(ControlSection *section) { 77 | section->print(""); 78 | } 79 | 80 | // pos should point to the first character that has sourcecode inside the control section controlSection 81 | // return value should be first character of the control section end part (ie first char of {% endfor %} type bit) 82 | int Template::eatSection( int pos, ControlSection *controlSection ) { 83 | // int pos = 0; 84 | // vector tokenStack; 85 | // string updatedString = ""; 86 | while( true ) { 87 | // cout << "pos: " << pos << endl; 88 | size_t controlChangeBegin = sourceCode.find( "{%", pos ); 89 | // cout << "controlChangeBegin: " << controlChangeBegin << endl; 90 | if( controlChangeBegin == string::npos ) { 91 | //updatedString += doSubstitutions( sourceCode.substr( pos ), valueByName ); 92 | std::unique_ptr code(new Code()); 93 | code->startPos = pos; 94 | code->endPos = sourceCode.length(); 95 | // code->templateCode = sourceCode.substr( pos, sourceCode.length() - pos ); 96 | code->templateCode = sourceCode.substr( code->startPos, code->endPos - code->startPos ); 97 | controlSection->sections.push_back( std::move(code) ); 98 | return sourceCode.length(); 99 | } else { 100 | size_t controlChangeEnd = sourceCode.find( "%}", controlChangeBegin ); 101 | if( controlChangeEnd == string::npos ) { 102 | throw render_error( "control section unterminated: " + sourceCode.substr( controlChangeBegin, 40 ) ); 103 | } 104 | string controlChange = trim( sourceCode.substr( controlChangeBegin + 2, controlChangeEnd - controlChangeBegin - 2 ) ); 105 | vector splitControlChange = split( controlChange, " " ); 106 | if( splitControlChange[0] == "endfor" || splitControlChange[0] == "endif") { 107 | if( splitControlChange.size() != 1 ) { 108 | throw render_error("control section {% " + controlChange + " unrecognized" ); 109 | } 110 | std::unique_ptr code(new Code()); 111 | code->startPos = pos; 112 | code->endPos = controlChangeBegin; 113 | code->templateCode = sourceCode.substr( code->startPos, code->endPos - code->startPos ); 114 | controlSection->sections.push_back( std::move(code) ); 115 | return controlChangeBegin; 116 | // if( tokenStack.size() == 0 ) { 117 | // throw render_error("control section {% " + controlChange + " unexpected: no current control stack items" ); 118 | // } 119 | // if( tokenStack[ tokenStack.size() - 1 ] != "for" ) { 120 | // throw render_error("control section {% " + controlChange + " unexpected: current last control stack item is: " + tokenStack[ tokenStack.size() - 1 ] ); 121 | // } 122 | // cout << "token stack old size: " << tokenStack.size() << endl; 123 | // tokenStack.erase( tokenStack.end() - 1, tokenStack.end() - 1 ); 124 | // string varToRemove = varNameStack[ (int)tokenStack.size() - 1 ]; 125 | // valueByName.erase( varToRemove ); 126 | // varNameStack.erase( tokenStack.end() - 1, tokenStack.end() - 1 ); 127 | // cout << "token stack new size: " << tokenStack.size() << endl; 128 | } else if( splitControlChange[0] == "for" ) { 129 | std::unique_ptr code(new Code()); 130 | code->startPos = pos; 131 | code->endPos = controlChangeBegin; 132 | code->templateCode = sourceCode.substr( code->startPos, code->endPos - code->startPos ); 133 | controlSection->sections.push_back( std::move(code) ); 134 | 135 | string varname = splitControlChange[1]; 136 | if( splitControlChange[2] != "in" ) { 137 | throw render_error("control section {% " + controlChange + " unexpected: second word should be 'in'" ); 138 | } 139 | string rangeString = ""; 140 | for( int i = 3; i < (int)splitControlChange.size(); i++ ) { 141 | rangeString += splitControlChange[i]; 142 | } 143 | rangeString = replaceGlobal( rangeString, " ", "" ); 144 | vector splitRangeString = split( rangeString, "(" ); 145 | if( splitRangeString[0] == "range" ) { 146 | if( splitRangeString.size() != 2 ) { 147 | throw render_error("control section " + controlChange + " unexpected: should be in format 'range(somevar)' or 'range(somenumber)'" ); 148 | } 149 | string name = split( splitRangeString[1], ")" )[0]; 150 | // cout << "for range name: " << name << endl; 151 | int endValue; 152 | if( isNumber( name, &endValue ) ) { 153 | } else { 154 | if( valueByName.find( name ) != valueByName.end() ) { 155 | IntValue *intValue = dynamic_cast< IntValue * >( valueByName[ name ].get() ); 156 | if( intValue == 0 ) { 157 | throw render_error("for loop range var " + name + " must be an int (but it's not)"); 158 | } 159 | endValue = intValue->value; 160 | } else { 161 | throw render_error("for loop range var " + name + " not recognized"); 162 | } 163 | } 164 | int beginValue = 0; // default for now... 165 | // cout << "for loop start=" << beginValue << " end=" << endValue << endl; 166 | std::unique_ptr forSection(new ForRangeSection()); 167 | forSection->startPos = controlChangeEnd + 2; 168 | forSection->loopStart = beginValue; 169 | forSection->loopEnd = endValue; 170 | forSection->varName = varname; 171 | pos = eatSection( controlChangeEnd + 2, forSection.get() ); 172 | size_t controlEndEndPos = sourceCode.find("%}", pos ); 173 | if( controlEndEndPos == string::npos ) { 174 | throw render_error("No control end section found at: " + sourceCode.substr(pos ) ); 175 | } 176 | string controlEnd = sourceCode.substr( pos, controlEndEndPos - pos + 2 ); 177 | string controlEndNorm = replaceGlobal( controlEnd, " ", "" ); 178 | if( controlEndNorm != "{%endfor%}" ) { 179 | throw render_error("No control end section found, expected '{% endfor %}', got '" + controlEnd + "'" ); 180 | } 181 | forSection->endPos = controlEndEndPos + 2; 182 | controlSection->sections.push_back(std::move(forSection)); 183 | pos = controlEndEndPos + 2; 184 | } else { 185 | const std::string name = rangeString; 186 | if (valueByName.find( name ) != valueByName.end() ) { 187 | TupleValue *value = dynamic_cast< TupleValue * >( valueByName[ name ].get() ); 188 | if( value == 0 ) { 189 | throw render_error("for loop var " + name + " must be a range or a vector (but it's neither)"); 190 | } 191 | } else { 192 | throw render_error("for loop var " + name + " not recognized"); 193 | } 194 | std::unique_ptr forSection(new ForSection()); 195 | forSection->varName = varname; 196 | forSection->tupVarName = name; 197 | 198 | pos = eatSection( controlChangeEnd + 2, forSection.get() ); 199 | controlSection->sections.push_back(std::move(forSection)); 200 | size_t controlEndEndPos = sourceCode.find("%}", pos ); 201 | if( controlEndEndPos == string::npos ) { 202 | throw render_error("No control end section found at: " + sourceCode.substr(pos ) ); 203 | } 204 | string controlEnd = sourceCode.substr( pos, controlEndEndPos - pos + 2 ); 205 | string controlEndNorm = replaceGlobal( controlEnd, " ", "" ); 206 | if( controlEndNorm != "{%endfor%}" ) { 207 | throw render_error("No control end section found, expected '{% endfor %}', got '" + controlEnd + "'" ); 208 | } 209 | pos = controlEndEndPos + 2; 210 | } 211 | } else if (splitControlChange[0] == "if") { 212 | std::unique_ptr code(new Code()); 213 | code->startPos = pos; 214 | code->endPos = controlChangeBegin; 215 | code->templateCode = sourceCode.substr(code->startPos, code->endPos - code->startPos); 216 | controlSection->sections.push_back(std::move(code)); 217 | const string word = splitControlChange[1]; 218 | if (JINJA2_TRUE == word) { 219 | ; 220 | } else if (JINJA2_FALSE == word) { 221 | ; 222 | } 223 | else if (JINJA2_NOT == word) { 224 | ; 225 | } 226 | else { 227 | ; 228 | } 229 | std::unique_ptr ifSection(new IfSection(controlChange)); 230 | 231 | pos = eatSection(controlChangeEnd + 2, ifSection.get()); 232 | controlSection->sections.push_back(std::move(ifSection)); 233 | size_t controlEndEndPos = sourceCode.find("%}", pos); 234 | if (controlEndEndPos == string::npos) { 235 | throw render_error("No control end of any section found at: " + sourceCode.substr(pos)); 236 | } 237 | string controlEnd = sourceCode.substr(pos, controlEndEndPos - pos + 2); 238 | string controlEndNorm = replaceGlobal(controlEnd, " ", ""); 239 | if (controlEndNorm != "{%endif%}") { 240 | throw render_error("No control end section found, expected '{% endif %}', got '" + controlEnd + "'"); 241 | } 242 | //forSection->endPos = controlEndEndPos + 2; 243 | pos = controlEndEndPos + 2; 244 | 245 | } else { 246 | throw render_error("control section {% " + controlChange + " unexpected" ); 247 | } 248 | } 249 | } 250 | 251 | // vector controlSplit = split( sourceCode, "{%" ); 252 | //// int startI = 1; 253 | //// if( controlSplit.substr(0,2) == "{%" ) { 254 | //// startI = 0; 255 | //// } 256 | // string updatedString = ""; 257 | // for( int i = 0; i < (int)controlSplit.size(); i++ ) { 258 | // if( controlSplit[i].substr(0,2) == "{%" ) { 259 | // vector splitControlPair = split(controlSplit[i], "%}" ); 260 | // string controlString = splitControlPair[0]; 261 | // } else { 262 | // updatedString += doSubstitutions( controlSplit[i], valueByName ); 263 | // } 264 | // } 265 | //// string templatedString = doSubstitutions( sourceCode, valueByName ); 266 | // return updatedString; 267 | } 268 | STATIC std::string Template::doSubstitutions( std::string sourceCode, const ValueMap &valueByName ) { 269 | int startI = 1; 270 | if( sourceCode.substr(0,2) == "{{" ) { 271 | startI = 0; 272 | } 273 | string templatedString = ""; 274 | vector splitSource = split( sourceCode, "{{" ); 275 | if( startI == 1 ) { 276 | templatedString = splitSource[0]; 277 | } 278 | for( size_t i = startI; i < splitSource.size(); i++ ) { 279 | if (0 == i && splitSource.size() > 1 && splitSource[i].empty()) // Ignoring initial empty section if there are other sections. 280 | { 281 | continue; 282 | } 283 | 284 | vector thisSplit = split( splitSource[i], "}}" ); 285 | string name = trim( thisSplit[0] ); 286 | // cout << "name: " << name << endl; 287 | auto p = valueByName.find( name ); 288 | if( p == valueByName.end() ) { 289 | throw render_error( "name " + name + " not defined" ); 290 | } 291 | auto value = p->second; 292 | templatedString += value->render(); 293 | if( thisSplit.size() > 0 ) { 294 | templatedString += thisSplit[1]; 295 | } 296 | } 297 | return templatedString; 298 | } 299 | 300 | void IfSection::parseIfCondition(const std::string& expression) { 301 | const std::vector splittedExpression = split(expression, " "); 302 | if (splittedExpression.empty() || splittedExpression[0] != "if") { 303 | throw render_error("if statement expected."); 304 | } 305 | 306 | std::size_t expressionIndex = 1; 307 | if (splittedExpression.size() < expressionIndex + 1) { 308 | throw render_error("Any expression expected after if statement."); 309 | } 310 | m_isNegation = (JINJA2_NOT == splittedExpression[expressionIndex]); 311 | expressionIndex += (m_isNegation) ? 1 : 0; 312 | if (splittedExpression.size() < expressionIndex + 1) { 313 | if (!m_isNegation) 314 | throw render_error("Any expression expected after if statement."); 315 | else 316 | throw render_error("Any expression expected after if not statement."); 317 | } 318 | m_variableName = splittedExpression[expressionIndex]; 319 | if (splittedExpression.size() > expressionIndex + 1) { 320 | throw render_error(std::string("Unexpected expression after variable name: ") + splittedExpression[expressionIndex + 1]); 321 | } 322 | } 323 | 324 | bool IfSection::computeExpression(const ValueMap &valueByName) const { 325 | if (JINJA2_TRUE == m_variableName) { 326 | return true ^ m_isNegation; 327 | } 328 | else if (JINJA2_FALSE == m_variableName) { 329 | return false ^ m_isNegation; 330 | } 331 | else { 332 | const bool valueExists = valueByName.count(m_variableName) > 0; 333 | if (!valueExists) { 334 | return false ^ m_isNegation; 335 | } 336 | return valueByName.at(m_variableName)->isTrue() ^ m_isNegation; 337 | } 338 | } 339 | 340 | } 341 | 342 | 343 | -------------------------------------------------------------------------------- /src/Jinja2CppLight.h: -------------------------------------------------------------------------------- 1 | // Copyright Hugh Perkins 2015 hughperkins at gmail 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public License, 4 | // v. 2.0. If a copy of the MPL was not distributed with this file, You can 5 | // obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | // the intent here is to create a templates library that: 8 | // - is based on Jinja2 syntax 9 | // - doesn't depend on boost, qt, etc ... 10 | 11 | // for now, will handle: 12 | // - variable substitution, ie {{myvar}} 13 | // - for loops, ie {% for i in range(myvar) %} 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "stringhelper.h" 25 | 26 | #define VIRTUAL virtual 27 | #define STATIC static 28 | 29 | namespace Jinja2CppLight { 30 | 31 | class render_error : public std::runtime_error { 32 | public: 33 | render_error( const std::string &what ) : 34 | std::runtime_error( what ) { 35 | } 36 | }; 37 | 38 | class Value { 39 | public: 40 | virtual ~Value() {} 41 | virtual std::string render() = 0; 42 | virtual bool isTrue() const = 0; 43 | }; 44 | class IntValue : public Value { 45 | public: 46 | int value; 47 | IntValue( int value ) : 48 | value( value ) { 49 | } 50 | virtual std::string render() { 51 | return toString( value ); 52 | } 53 | bool isTrue() const { 54 | return value != 0; 55 | } 56 | }; 57 | class FloatValue : public Value { 58 | public: 59 | float value; 60 | FloatValue( float value ) : 61 | value( value ) { 62 | } 63 | virtual std::string render() { 64 | return toString( value ); 65 | } 66 | bool isTrue() const { 67 | return value != 0.0; 68 | } 69 | }; 70 | class StringValue : public Value { 71 | public: 72 | std::string value; 73 | StringValue( std::string value ) : 74 | value( value ) { 75 | } 76 | virtual std::string render() { 77 | return value; 78 | } 79 | bool isTrue() const { 80 | return !value.empty(); 81 | } 82 | }; 83 | 84 | class TupleValue : public Value 85 | { 86 | public: 87 | std::vector> values; 88 | 89 | TupleValue &addValue( int value ) { 90 | values.push_back(std::make_shared(value)); 91 | return *this; 92 | } 93 | TupleValue &addValue( double value ) { 94 | values.push_back(std::make_shared(value)); 95 | return *this; 96 | } 97 | TupleValue &addValue( std::string value ) { 98 | values.push_back(std::make_shared(std::move(value))); 99 | return *this; 100 | } 101 | TupleValue &addValue( TupleValue value ) { 102 | values.push_back(std::make_shared(std::move(value))); 103 | return *this; 104 | } 105 | 106 | bool isTrue() const { 107 | return !values.empty(); 108 | } 109 | 110 | virtual std::string render() { 111 | std::string result = "{"; 112 | bool isFirst = true; 113 | 114 | for (auto& val : values) { 115 | if (isFirst) 116 | isFirst = false; 117 | else 118 | result += ", "; 119 | 120 | result += val ? val->render() : ""; 121 | } 122 | 123 | result += "}"; 124 | return result; 125 | } 126 | 127 | template 128 | static TupleValue create(Args&& ... args) { 129 | TupleValue result; 130 | 131 | createImpl (result, std::forward(args)...); 132 | 133 | return result; 134 | } 135 | private: 136 | static void createImpl(TupleValue&) { 137 | } 138 | 139 | template 140 | static void createImpl(TupleValue& result, Arg&& arg, Args&& ... args) { 141 | result.addValue(std::forward(arg)); 142 | createImpl (result, std::forward(args)...); 143 | } 144 | }; 145 | 146 | class Root; 147 | class ControlSection; 148 | typedef std::map < std::string, std::shared_ptr > ValueMap; 149 | 150 | class Template { 151 | public: 152 | std::string sourceCode; 153 | 154 | ValueMap valueByName; 155 | 156 | // [[[cog 157 | // import cog_addheaders 158 | // cog_addheaders.add(classname='Template') 159 | // ]]] 160 | // generated, using cog: 161 | Template( std::string sourceCode ); 162 | STATIC bool isNumber( std::string astring, int *p_value ); 163 | VIRTUAL ~Template(); 164 | Template &setValue( std::string name, int value ); 165 | Template &setValue( std::string name, float value ); 166 | Template &setValue( std::string name, std::string value ); 167 | Template&setValue( std::string name, TupleValue value); 168 | std::string render(); 169 | void print(ControlSection *section); 170 | int eatSection( int pos, ControlSection *controlSection ); 171 | STATIC std::string doSubstitutions( std::string sourceCode, const ValueMap &valueByName ); 172 | 173 | // [[[end]]] 174 | }; 175 | 176 | class ControlSection { 177 | public: 178 | 179 | virtual ~ControlSection() { sections.clear(); } 180 | 181 | std::vector< std::unique_ptr >sections; 182 | virtual std::string render( ValueMap &valueByName ) = 0; 183 | virtual void print() { 184 | print(""); 185 | } 186 | virtual void print(std::string prefix) = 0; 187 | }; 188 | 189 | class Container : public ControlSection { 190 | public: 191 | // std::vector< ControlSection * >sections; 192 | int sourceCodePosStart; 193 | int sourceCodePosEnd; 194 | 195 | // std::string render( ValueMap valueByName ); 196 | virtual void print( std::string prefix ) { 197 | std::cout << prefix << "Container ( " << sourceCodePosStart << ", " << sourceCodePosEnd << " ) {" << std::endl; 198 | for( int i = 0; i < (int)sections.size(); i++ ) { 199 | sections[i]->print( prefix + " " ); 200 | } 201 | std::cout << prefix << "}" << std::endl; 202 | } 203 | }; 204 | 205 | class ForRangeSection : public ControlSection { 206 | public: 207 | int loopStart; 208 | int loopEnd; 209 | std::string varName; 210 | int startPos; 211 | int endPos; 212 | std::string render( ValueMap &valueByName ) { 213 | std::string result = ""; 214 | // bool nameExistsBefore = false; 215 | if( valueByName.find( varName ) != valueByName.end() ) { 216 | throw render_error("variable " + varName + " already exists in this context" ); 217 | } 218 | valueByName[varName] = std::make_shared( 0 ); 219 | for (auto i = loopStart; i < loopEnd; ++i ){ 220 | dynamic_cast(valueByName[varName].get())->value = i; 221 | for( size_t j = 0; j < sections.size(); j++ ) { 222 | result += sections[j]->render( valueByName ); 223 | } 224 | } 225 | valueByName.erase( varName ); 226 | return result; 227 | } 228 | //Container *contents; 229 | virtual void print( std::string prefix ) { 230 | std::cout << prefix << "For ( " << varName << " in range( ) {" << std::endl; 231 | for( int i = 0; i < (int)sections.size(); i++ ) { 232 | sections[i]->print( prefix + " " ); 233 | } 234 | std::cout << prefix << "}" << std::endl; 235 | } 236 | }; 237 | 238 | class ForSection : public ControlSection { 239 | public: 240 | std::string varName; 241 | std::string tupVarName; 242 | virtual std::string render( ValueMap &valueByName ) { 243 | std::string result = ""; 244 | if( valueByName.find( varName ) != valueByName.end() ) { 245 | throw render_error("variable " + varName + " already exists in this context" ); 246 | } 247 | 248 | const std::shared_ptr &val = valueByName.at( tupVarName ); // throws if something happened to the TupleValue 249 | const TupleValue *tupValue = dynamic_cast< TupleValue * >( val.get() ); 250 | if (!tupValue) { 251 | throw render_error("variable " + tupVarName + " no longer valid in context" ); 252 | } 253 | const std::vector> &tupValues = tupValue->values; 254 | for ( auto itr = tupValues.cbegin(); itr != tupValues.cend(); ++itr ) { 255 | valueByName[ varName ] = *itr; 256 | for( std::size_t j = 0; j < sections.size(); ++j) { 257 | result += sections[j]->render( valueByName ); 258 | } 259 | valueByName.erase( varName ); 260 | } 261 | 262 | return result; 263 | } 264 | virtual void print( std::string prefix ) { 265 | std::cout << prefix << "For ( " << varName << " in " << tupVarName << " ) {" << std::endl; 266 | for( std::size_t i = 0; i < sections.size(); i++ ){ 267 | sections[i]->print( prefix + " " ); 268 | } 269 | std::cout << prefix << "}" << std::endl; 270 | } 271 | }; 272 | 273 | class Code : public ControlSection { 274 | public: 275 | // vector< ControlSection * >sections; 276 | int startPos; 277 | int endPos; 278 | std::string templateCode; 279 | 280 | std::string render(); 281 | virtual void print( std::string prefix ) { 282 | std::cout << prefix << "Code ( " << startPos << ", " << endPos << " ) {" << std::endl; 283 | for( int i = 0; i < (int)sections.size(); i++ ) { 284 | sections[i]->print( prefix + " " ); 285 | } 286 | std::cout << prefix << "}" << std::endl; 287 | } 288 | virtual std::string render( ValueMap &valueByName ) { 289 | // std::string templateString = sourceCode.substr( startPos, endPos - startPos ); 290 | // std::cout << "Code section, rendering [" << templateCode << "]" << std::endl; 291 | std::string processed = Template::doSubstitutions( templateCode, valueByName ); 292 | // std::cout << "Code section, after rendering: [" << processed << "]" << std::endl; 293 | return processed; 294 | } 295 | }; 296 | 297 | class Root : public ControlSection { 298 | public: 299 | virtual ~Root() {} 300 | virtual std::string render( ValueMap &valueByName ) { 301 | std::string resultString = ""; 302 | for( int i = 0; i < (int)sections.size(); i++ ) { 303 | resultString += sections[i]->render( valueByName ); 304 | } 305 | return resultString; 306 | } 307 | virtual void print(std::string prefix) { 308 | std::cout << prefix << "Root {" << std::endl; 309 | for( int i = 0; i < (int)sections.size(); i++ ) { 310 | sections[i]->print( prefix + " " ); 311 | } 312 | std::cout << prefix << "}" << std::endl; 313 | } 314 | }; 315 | 316 | class IfSection : public ControlSection { 317 | public: 318 | IfSection(const std::string& expression) { 319 | parseIfCondition(expression); 320 | } 321 | 322 | std::string render(ValueMap &valueByName) { 323 | std::stringstream ss; 324 | const bool expressionValue = computeExpression(valueByName); 325 | if (expressionValue) { 326 | for (size_t j = 0; j < sections.size(); j++) { 327 | ss << sections[j]->render(valueByName); 328 | } 329 | } 330 | const std::string renderResult = ss.str(); 331 | return renderResult; 332 | } 333 | 334 | void print(std::string prefix) { 335 | std::cout << prefix << "if ( " 336 | << ((m_isNegation) ? "not " : "") 337 | << m_variableName << " ) {" << std::endl; 338 | if (true) { 339 | for (int i = 0; i < (int)sections.size(); i++) { 340 | sections[i]->print(prefix + " "); 341 | } 342 | } 343 | std::cout << prefix << "}" << std::endl; 344 | } 345 | 346 | private: 347 | //? It determines m_isNegation and m_variableName from @param[in] expression. 348 | //? @param[in] expression E.g. "if not myVariable" where myVariable is set by myTemplate.setValue( "myVariable", ); 349 | //? The result of this statement is false if myVariable is initialized. 350 | void parseIfCondition(const std::string& expression); 351 | 352 | bool computeExpression(const ValueMap &valueByName) const; 353 | 354 | bool m_isNegation; ///< Tells whether is there "if not" or just "if" at the begin of expression. 355 | std::string m_variableName; ///< This simple "if" implementation allows single variable condition only. 356 | }; 357 | 358 | } 359 | 360 | -------------------------------------------------------------------------------- /src/stringhelper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Hugh Perkins 2014, 2015 hughperkins at gmail 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public License, 4 | // v. 2.0. If a copy of the MPL was not distributed with this file, You can 5 | // obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | #include 8 | #include 9 | #include 10 | using namespace std; 11 | 12 | #include "stringhelper.h" 13 | 14 | vector split(const string &str, const string &separator ) { 15 | vector splitstring; 16 | int start = 0; 17 | int npos = (int)str.find(separator); 18 | while (npos != (int)str.npos ) { 19 | splitstring.push_back( str.substr(start, npos-start) ); 20 | start = npos + (int)separator.length(); 21 | npos = (int)str.find(separator, start); 22 | } 23 | splitstring.push_back( str.substr( start ) ); 24 | return splitstring; 25 | } 26 | 27 | string trim( const string &target ) { 28 | 29 | int origlen = (int)target.size(); 30 | int startpos = -1; 31 | for( int i = 0; i < origlen; i++ ) { 32 | if( target[i] != ' ' && target[i] != '\r' && target[i] != '\n' ) { 33 | startpos = i; 34 | break; 35 | } 36 | } 37 | int endpos = -1; 38 | for( int i = origlen - 1; i >= 0; i-- ) { 39 | if( target[i] != ' ' && target[i] != '\r' && target[i] != '\n' ) { 40 | endpos = i; 41 | break; 42 | } 43 | } 44 | if( startpos == -1 || endpos == -1 ) { 45 | return ""; 46 | } 47 | return target.substr(startpos, endpos-startpos + 1 ); 48 | } 49 | 50 | string replace( string targetString, string oldValue, string newValue ) { 51 | size_t pos = targetString.find( oldValue ); 52 | if( pos == string::npos ) { 53 | return targetString; 54 | } 55 | return targetString.replace( pos, oldValue.length(), newValue ); 56 | } 57 | string replaceGlobal( string targetString, string oldValue, string newValue ) { 58 | int pos = 0; 59 | string resultString = ""; 60 | size_t targetPos = targetString.find( oldValue, pos ); 61 | while( targetPos != string::npos ) { 62 | string preOld = targetString.substr( pos, targetPos - pos ); 63 | resultString += preOld + newValue; 64 | pos = targetPos + oldValue.length(); 65 | targetPos = targetString.find( oldValue, pos ); 66 | } 67 | resultString += targetString.substr(pos); 68 | return resultString; 69 | } 70 | 71 | std::string toLower(std::string in ) { 72 | int len = static_cast( in.size() ); 73 | char *buffer = new char[len + 1]; 74 | for( int i = 0; i < len; i++ ) { 75 | char thischar = in[i]; 76 | thischar = tolower(thischar); 77 | buffer[i] = thischar; 78 | } 79 | buffer[len] = 0; 80 | std::string result = std::string(buffer); 81 | delete[] buffer; 82 | return result; 83 | } 84 | 85 | void strcpy_safe( char *destination, char const*source, int maxLength ) { 86 | int i = 0; 87 | for( i = 0; i < maxLength; i++ ) { 88 | destination[i] = source[i]; 89 | if( source[i] == 0 ) { 90 | break; 91 | } 92 | } 93 | destination[i] = 0; 94 | } 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/stringhelper.h: -------------------------------------------------------------------------------- 1 | // Copyright Hugh Perkins 2014, 2015 hughperkins at gmail 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public License, 4 | // v. 2.0. If a copy of the MPL was not distributed with this file, You can 5 | // obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | // #include "ClConvolveDllExport.h" 16 | 17 | class IHasToString { 18 | public: 19 | virtual std::string toString() = 0; 20 | }; 21 | 22 | //std::string toString( IHasToString *val ); // { // not terribly efficient, but works... 23 | // std::ostringstream myostringstream; 24 | // myostringstream << val->toString(); 25 | // return myostringstream.str(); 26 | //} 27 | 28 | template 29 | std::string toString(T val ) { // not terribly efficient, but works... 30 | std::ostringstream myostringstream; 31 | myostringstream << val; 32 | return myostringstream.str(); 33 | } 34 | 35 | std::vector split(const std::string &str, const std::string &separator = " " ); 36 | std::string trim( const std::string &target ); 37 | 38 | inline float atof( std::string stringvalue ) { 39 | return (float)std::atof(stringvalue.c_str()); 40 | } 41 | inline int atoi( std::string stringvalue ) { 42 | return std::atoi(stringvalue.c_str()); 43 | } 44 | 45 | // returns empty string if off the end of the number of available tokens 46 | inline std::string getToken( std::string targetstring, int tokenIndexFromZero, std::string separator = " " ) { 47 | std::vector splitstring = split( targetstring, separator ); 48 | if( tokenIndexFromZero < (int)splitstring.size() ) { 49 | return splitstring[tokenIndexFromZero]; 50 | } else { 51 | return ""; 52 | } 53 | } 54 | 55 | std::string replace( std::string targetString, std::string oldValue, std::string newValue ); 56 | std::string replaceGlobal( std::string targetString, std::string oldValue, std::string newValue ); 57 | 58 | std::string toLower(std::string in ); 59 | 60 | void strcpy_safe( char *destination, char const*source, int maxLength ); 61 | 62 | -------------------------------------------------------------------------------- /test/gtest_supp.h: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #include "stringhelper.h" 3 | 4 | inline ::testing::AssertionResult AssertFloatsNear( const char *expr_one, const char *expr_two, 5 | float one, float two ) { 6 | float diff = one - two; 7 | float absdiff = diff > 0 ? diff : - diff; 8 | float absone = one > 0 ? one : -one; 9 | if( absdiff <= absone * 0.0001f ) { 10 | return ::testing::AssertionSuccess(); 11 | } 12 | std::string onestr = toString(one); 13 | std::string twostr = toString(two); 14 | return ::testing::AssertionFailure() 15 | << expr_one << " and " << expr_two << " differ: " << onestr << " vs " << twostr; 16 | } 17 | 18 | #define EXPECT_FLOAT_NEAR( one, two) EXPECT_PRED_FORMAT2( AssertFloatsNear, one, two ) 19 | #define ASSERT_FLOAT_NEAR( one, two) ASSERT_PRED_FORMAT2( AssertFloatsNear, one, two ) 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/testJinja2CppLight.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Hugh Perkins 2015 hughperkins at gmail 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public License, 4 | // v. 2.0. If a copy of the MPL was not distributed with this file, You can 5 | // obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "gtest/gtest.h" 12 | #include "test/gtest_supp.h" 13 | 14 | #include "Jinja2CppLight.h" 15 | 16 | using namespace std; 17 | using namespace Jinja2CppLight; 18 | 19 | TEST( testJinja2CppLight, basicsubstitution ) { 20 | string source = R"DELIM( 21 | This is my {{avalue}} template. It's {{secondvalue}}... 22 | Today's weather is {{weather}}. Values list: {{valuesList}} 23 | )DELIM"; 24 | 25 | Template mytemplate( source ); 26 | mytemplate.setValue( "avalue", 3 ); 27 | mytemplate.setValue( "secondvalue", 12.123f ); 28 | mytemplate.setValue( "weather", "rain" ); 29 | mytemplate.setValue( "valuesList", TupleValue::create(10, 20.256, "Hello World!")); 30 | string result = mytemplate.render(); 31 | cout << result << endl; 32 | string expectedResult = R"DELIM( 33 | This is my 3 template. It's 12.123... 34 | Today's weather is rain. Values list: {10, 20.256, Hello World!} 35 | )DELIM"; 36 | EXPECT_EQ( expectedResult, result ); 37 | } 38 | /* 39 | TEST( testJinja2CppLight, vectorsubstitution ) { 40 | string source = R"DELIM( 41 | Here's the list: {{ values }}... 42 | {% if maybe %}{{ maybe }}{% endif %} 43 | {% if maybenot %}{{ maybenot }}{% endif %} 44 | We're going to print it anyway: {{ maybenot }} 45 | )DELIM"; 46 | 47 | std::vector values = { "one", "two", "three" }; 48 | std::vector maybe = { "There is one" }; 49 | std::vector maybenot; 50 | Template mytemplate( source ); 51 | mytemplate.setValue( "values", values ); 52 | mytemplate.setValue( "maybe", maybe ); 53 | mytemplate.setValue( "maybenot", maybenot ); 54 | string result = mytemplate.render(); 55 | const string expectedResult = R"DELIM( 56 | Here's the list: one two three... 57 | There is one 58 | 59 | We're going to print it anyway: 60 | )DELIM"; 61 | EXPECT_EQ( expectedResult, result ); 62 | } 63 | TEST( testJinja2CppLight, forloop ) { 64 | string source = R"DELIM( 65 | Shopping list:{% for item in items %} 66 | - {{ item }}{% endfor %} 67 | )DELIM"; 68 | 69 | std::vector shopList = { "eggs", "milk", "vodka" }; 70 | Template mytemplate0( source ); 71 | mytemplate0.setValue( "items", shopList ); 72 | string result = mytemplate0.render(); 73 | string expectedResult = R"DELIM( 74 | Shopping list: 75 | - eggs 76 | - milk 77 | - vodka 78 | )DELIM"; 79 | EXPECT_EQ( expectedResult, result ); 80 | 81 | shopList.clear(); 82 | Template mytemplate1( source ); 83 | mytemplate1.setValue( "items", shopList ); 84 | result = mytemplate1.render(); 85 | expectedResult = R"DELIM( 86 | Shopping list: 87 | )DELIM"; 88 | EXPECT_EQ( expectedResult, result ); 89 | } 90 | */ 91 | TEST( testSpeedTemplates, namemissing ) { 92 | string source = R"DELIM( 93 | This is my {{avalue}} template. 94 | )DELIM"; 95 | 96 | Template mytemplate( source ); 97 | bool threw = false; 98 | try { 99 | string result = mytemplate.render(); 100 | } catch( render_error &e ) { 101 | EXPECT_EQ( string("name avalue not defined"), e.what() ); 102 | threw = true; 103 | } 104 | EXPECT_EQ( true, threw ); 105 | } 106 | TEST( testSpeedTemplates, loop ) { 107 | string source = R"DELIM( 108 | {% for i in range(its) %} 109 | a[{{i}}] = image[{{i}}]; 110 | {% endfor %} 111 | )DELIM"; 112 | 113 | Template mytemplate( source ); 114 | mytemplate.setValue( "its", 3 ); 115 | string result = mytemplate.render(); 116 | cout << result << endl; 117 | string expectedResult = R"DELIM( 118 | 119 | a[0] = image[0]; 120 | 121 | a[1] = image[1]; 122 | 123 | a[2] = image[2]; 124 | 125 | )DELIM"; 126 | EXPECT_EQ( expectedResult, result ); 127 | } 128 | 129 | TEST( testSpeedTemplates, tupleloop ) { 130 | string source = R"DELIM( 131 | {% for i in its %} 132 | a[{{i}}] = image[{{i}}]; 133 | {% endfor %} 134 | )DELIM"; 135 | 136 | Template mytemplate( source ); 137 | mytemplate.setValue( "its", TupleValue::create(0, 1.1, "2abc") ); 138 | string result = mytemplate.render(); 139 | cout << result << endl; 140 | string expectedResult = R"DELIM( 141 | 142 | a[0] = image[0]; 143 | 144 | a[1.1] = image[1.1]; 145 | 146 | a[2abc] = image[2abc]; 147 | 148 | )DELIM"; 149 | EXPECT_EQ( expectedResult, result ); 150 | } 151 | 152 | TEST( testSpeedTemplates, nestedloop ) { 153 | string source = R"DELIM( 154 | {% for i in range(its) %}a[{{i}}] = image[{{i}}]; 155 | {% for j in range(2) %}b[{{j}}] = image[{{j}}]; 156 | {% endfor %}{% endfor %} 157 | )DELIM"; 158 | 159 | Template mytemplate( source ); 160 | mytemplate.setValue( "its", 3 ); 161 | string result = mytemplate.render(); 162 | cout << "[" << result << "]" << endl; 163 | string expectedResult = R"DELIM( 164 | a[0] = image[0]; 165 | b[0] = image[0]; 166 | b[1] = image[1]; 167 | a[1] = image[1]; 168 | b[0] = image[0]; 169 | b[1] = image[1]; 170 | a[2] = image[2]; 171 | b[0] = image[0]; 172 | b[1] = image[1]; 173 | 174 | )DELIM"; 175 | EXPECT_EQ( expectedResult, result ); 176 | } 177 | 178 | TEST(testSpeedTemplates, ifTrueTest) { 179 | const std::string source = "abc{% if True %}def{% endif %}ghi"; 180 | Template mytemplate( source ); 181 | const string result = mytemplate.render(); 182 | std::cout << "[" << result << "]" << endl; 183 | const std::string expectedResult = "abcdefghi"; 184 | 185 | EXPECT_EQ(expectedResult, result); 186 | } 187 | 188 | TEST(testSpeedTemplates, ifFalseTest) { 189 | const std::string source = "abc{% if False %}def{% endif %}ghi"; 190 | Template mytemplate(source); 191 | const string result = mytemplate.render(); 192 | std::cout << "[" << result << "]" << endl; 193 | const std::string expectedResult = "abcghi"; 194 | 195 | EXPECT_EQ(expectedResult, result); 196 | } 197 | 198 | TEST(testSpeedTemplates, ifNotTrueTest) { 199 | const std::string source = "abc{% if not True %}def{% endif %}ghi"; 200 | Template mytemplate(source); 201 | const string result = mytemplate.render(); 202 | std::cout << "[" << result << "]" << endl; 203 | const std::string expectedResult = "abcghi"; 204 | 205 | EXPECT_EQ(expectedResult, result); 206 | } 207 | 208 | TEST(testSpeedTemplates, ifNotFalseTest) { 209 | const std::string source = "abc{% if not False %}def{% endif %}ghi"; 210 | Template mytemplate(source); 211 | const string result = mytemplate.render(); 212 | std::cout << "[" << result << "]" << endl; 213 | const std::string expectedResult = "abcdefghi"; 214 | 215 | EXPECT_EQ(expectedResult, result); 216 | } 217 | 218 | TEST(testSpeedTemplates, ifVariableExitsTest) { 219 | const std::string source = "abc{% if its %}def{% endif %}ghi"; 220 | 221 | { 222 | Template mytemplate(source); 223 | const std::string expectedResultNoVariable = "abcghi"; 224 | const std::string result = mytemplate.render(); 225 | EXPECT_EQ(expectedResultNoVariable, result); 226 | } 227 | 228 | { 229 | Template mytemplate(source); 230 | mytemplate.setValue("its", 3); 231 | const std::string result = mytemplate.render(); 232 | std::cout << "[" << result << "]" << endl; 233 | const std::string expectedResult = "abcdefghi"; 234 | EXPECT_EQ(expectedResult, result); 235 | } 236 | } 237 | 238 | TEST(testSpeedTemplates, ifVariableDoesntExitTest) { 239 | const std::string source = "abc{% if not its %}def{% endif %}ghi"; 240 | 241 | { 242 | Template mytemplate(source); 243 | const std::string expectedResultNoVariable = "abcdefghi"; 244 | const std::string result = mytemplate.render(); 245 | EXPECT_EQ(expectedResultNoVariable, result); 246 | } 247 | 248 | { 249 | Template mytemplate(source); 250 | mytemplate.setValue("its", 3); 251 | const std::string result = mytemplate.render(); 252 | std::cout << "[" << result << "]" << endl; 253 | const std::string expectedResult = "abcghi"; 254 | EXPECT_EQ(expectedResult, result); 255 | } 256 | } 257 | 258 | TEST(testSpeedTemplates, ifUnexpectedExpression) { 259 | const std::string source = "abc{% if its is defined %}def{% endif %}ghi"; 260 | Template myTemplate(source); 261 | bool threw = false; 262 | try { 263 | myTemplate.render(); 264 | } 265 | catch (const render_error &e) { 266 | EXPECT_EQ(std::string("Unexpected expression after variable name: is"), e.what()); 267 | threw = true; 268 | } 269 | EXPECT_EQ(true, threw); 270 | } 271 | -------------------------------------------------------------------------------- /test/teststringhelper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Hugh Perkins 2015 hughperkins at gmail 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public License, 4 | // v. 2.0. If a copy of the MPL was not distributed with this file, You can 5 | // obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | #include 8 | 9 | #include "stringhelper.h" 10 | 11 | #include "gtest/gtest.h" 12 | #include "test/gtest_supp.h" 13 | 14 | using namespace std; 15 | 16 | TEST( teststringhelper, split ) { 17 | string mystring = "MP10"; 18 | vector splitString = split( mystring, "MP" ); 19 | EXPECT_EQ( "", splitString[0] ); 20 | EXPECT_EQ( "10", splitString[1] ); 21 | } 22 | 23 | TEST( teststringhelper, split2 ) { 24 | string mystring = "MP10MPMP54"; 25 | vector splitString = split( mystring, "MP" ); 26 | EXPECT_EQ( "", splitString[0] ); 27 | EXPECT_EQ( "10", splitString[1] ); 28 | EXPECT_EQ( "", splitString[2] ); 29 | EXPECT_EQ( "54", splitString[3] ); 30 | } 31 | 32 | TEST( teststringhelper, split3 ) { 33 | string mystring = "42MP10MPMP54"; 34 | vector splitString = split( mystring, "MP" ); 35 | EXPECT_EQ( "42", splitString[0] ); 36 | EXPECT_EQ( "10", splitString[1] ); 37 | EXPECT_EQ( "", splitString[2] ); 38 | EXPECT_EQ( "54", splitString[3] ); 39 | } 40 | 41 | TEST( teststringhelper, tolower ) { 42 | string mystring = "3fAfef4FAD"; 43 | EXPECT_EQ( "3fafef4fad", toLower( mystring ) ); 44 | } 45 | 46 | TEST( teststringhelper, replace ) { 47 | string mystring = "hellonewworld"; 48 | EXPECT_EQ( "hellocoolworld", replace( mystring, "new", "cool" ) ); 49 | } 50 | 51 | TEST( teststringhelper, replaceglobal ) { 52 | string mystring = ""; 53 | mystring = "hello world"; 54 | EXPECT_EQ( "one world", replaceGlobal( mystring, "hello", "one" ) ); 55 | 56 | mystring = "hello hello"; 57 | EXPECT_EQ( "one one", replaceGlobal( mystring, "hello", "one" ) ); 58 | 59 | mystring = "hello hello hello"; 60 | EXPECT_EQ( "one one one", replaceGlobal( mystring, "hello", "one" ) ); 61 | 62 | mystring = "hellohellohello"; 63 | EXPECT_EQ( "oneoneone", replaceGlobal( mystring, "hello", "one" ) ); 64 | 65 | mystring = "hellonewwohellorldhellohellohello"; 66 | EXPECT_EQ( "onenewwoonerldoneoneone", replaceGlobal( mystring, "hello", "one" ) ); 67 | } 68 | 69 | TEST( teststringhelper, strcpy_safe ) { 70 | char const*source = "hello123"; 71 | char dest[1024]; 72 | memset( dest, 99, 1024 ); // ie, not zero :-) 73 | strcpy_safe( dest, source, 100 ); 74 | string target = string(dest); 75 | EXPECT_EQ( "hello123", target ); 76 | EXPECT_EQ( 0, dest[8] ); 77 | EXPECT_EQ( 99, dest[9] ); 78 | EXPECT_EQ( '3', dest[7] ); 79 | 80 | memset( dest, 99, 1024 ); // ie, not zero :-) 81 | strcpy_safe( dest, source, 3 ); 82 | target = string(dest); 83 | EXPECT_EQ( "hel", target ); 84 | EXPECT_EQ( 0, dest[3] ); 85 | EXPECT_EQ( 99, dest[4] ); 86 | EXPECT_EQ( 'l', dest[2] ); 87 | } 88 | 89 | -------------------------------------------------------------------------------- /thirdparty/cogapp/cog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ Cog code generation tool. 3 | http://nedbatchelder.com/code/cog 4 | 5 | Copyright 2004-2005, Ned Batchelder. 6 | """ 7 | 8 | import time 9 | start = time.clock() 10 | 11 | if __name__ == '__main__' and __package__ is None: 12 | from os import sys, path 13 | mydir = path.dirname(path.dirname(path.abspath(__file__))) 14 | #print mydir 15 | sys.path.append(mydir) 16 | 17 | import sys 18 | from cogapp import Cog 19 | 20 | ret = Cog().main(sys.argv) 21 | 22 | #print "Time: %.2f sec" % (time.clock() - start) 23 | sys.exit(ret) 24 | -------------------------------------------------------------------------------- /thirdparty/cogapp/cogapp/__init__.py: -------------------------------------------------------------------------------- 1 | """ Cog code generation tool. 2 | http://nedbatchelder.com/code/cog 3 | 4 | Copyright 2004-2012, Ned Batchelder. 5 | """ 6 | 7 | from __future__ import absolute_import 8 | 9 | from .cogapp import * 10 | -------------------------------------------------------------------------------- /thirdparty/cogapp/cogapp/__main__.py: -------------------------------------------------------------------------------- 1 | """Make Cog runnable directly from the module.""" 2 | import sys 3 | from cogapp import Cog 4 | 5 | sys.exit(Cog().main(sys.argv)) 6 | -------------------------------------------------------------------------------- /thirdparty/cogapp/cogapp/backward.py: -------------------------------------------------------------------------------- 1 | """Compatibility between Py2 and Py3.""" 2 | 3 | import sys 4 | 5 | PY3 = sys.version_info[0] == 3 6 | 7 | if PY3: 8 | string_types = (str,bytes) 9 | text_types = (str,) 10 | bytes_types = (bytes,) 11 | def b(s): 12 | return s.encode("latin-1") 13 | def to_bytes(s): 14 | return s.encode('utf8') 15 | else: 16 | string_types = (basestring,) 17 | text_types = (unicode,) 18 | bytes_types = (str,) 19 | def b(s): 20 | return s 21 | def to_bytes(s): 22 | return s 23 | 24 | # Pythons 2 and 3 differ on where to get StringIO 25 | try: 26 | from cStringIO import StringIO 27 | except ImportError: 28 | from io import StringIO 29 | -------------------------------------------------------------------------------- /thirdparty/cogapp/cogapp/cogapp.py: -------------------------------------------------------------------------------- 1 | """ Cog code generation tool. 2 | http://nedbatchelder.com/code/cog 3 | 4 | Copyright 2004-2015, Ned Batchelder. 5 | """ 6 | 7 | from __future__ import absolute_import, print_function 8 | 9 | import copy, getopt, hashlib, imp, os, re, shlex, sys, traceback, glob 10 | from .backward import PY3, StringIO, string_types, to_bytes 11 | 12 | __all__ = ['Cog', 'CogUsageError'] 13 | 14 | __version__ = '2.3' # History at the end of the file. 15 | 16 | usage = """\ 17 | cog - generate code with inlined Python code. 18 | 19 | cog [OPTIONS] [INFILE | @FILELIST] ... 20 | 21 | INFILE is the name of an input file, '-' will read from stdin. 22 | FILELIST is the name of a text file containing file names or 23 | other @FILELISTs. 24 | 25 | OPTIONS: 26 | -c Checksum the output to protect it against accidental change. 27 | -d Delete the generator code from the output file. 28 | -D name=val Define a global string available to your generator code. 29 | -e Warn if a file has no cog code in it. 30 | -I PATH Add PATH to the list of directories for data files and modules. 31 | -n ENCODING Use ENCODING when read and write files. 32 | -o OUTNAME Write the output to OUTNAME. 33 | -r Replace the input file with the output. 34 | -s STRING Suffix all generated output lines with STRING. 35 | -U Write the output with Unix newlines (only LF line-endings). 36 | -w CMD Use CMD if the output file needs to be made writable. 37 | A %s in the CMD will be filled with the filename. 38 | -x Excise all the generated output without running the generators. 39 | -z The end-output marker can be omitted, and is assumed at eof. 40 | -v Print the version of cog and exit. 41 | --begin-spec=val 42 | The pattern beginning cog inline instructions. Defaults 43 | to '[[[cog'. 44 | --end-spec=val 45 | The pattern ending cog inline instructions. Defaults 46 | to ']]]'. 47 | --end-output=val 48 | The pattern ending a cog block. Defaults to '[[[end]]]'. 49 | -h Print this help. 50 | """ 51 | 52 | # Other package modules 53 | from .whiteutils import * 54 | 55 | class CogError(Exception): 56 | """ Any exception raised by Cog. 57 | """ 58 | def __init__(self, msg, file='', line=0): 59 | if file: 60 | Exception.__init__(self, "%s(%d): %s" % (file, line, msg)) 61 | else: 62 | Exception.__init__(self, msg) 63 | 64 | class CogUsageError(CogError): 65 | """ An error in usage of command-line arguments in cog. 66 | """ 67 | pass #pragma: no cover 68 | 69 | class CogInternalError(CogError): 70 | """ An error in the coding of Cog. Should never happen. 71 | """ 72 | pass #pragma: no cover 73 | 74 | class CogGeneratedError(CogError): 75 | """ An error raised by a user's cog generator. 76 | """ 77 | pass #pragma: no cover 78 | 79 | class Redirectable: 80 | """ An object with its own stdout and stderr files. 81 | """ 82 | def __init__(self): 83 | self.stdout = sys.stdout 84 | self.stderr = sys.stderr 85 | 86 | def setOutput(self, stdout=None, stderr=None): 87 | """ Assign new files for standard out and/or standard error. 88 | """ 89 | if stdout: 90 | self.stdout = stdout 91 | if stderr: 92 | self.stderr = stderr 93 | 94 | def prout(self, s, end="\n"): 95 | print(s, file=self.stdout, end=end) 96 | 97 | def prerr(self, s, end="\n"): 98 | print(s, file=self.stderr, end=end) 99 | 100 | 101 | class CogGenerator(Redirectable): 102 | """ A generator pulled from a source file. 103 | """ 104 | def __init__(self): 105 | Redirectable.__init__(self) 106 | self.markers = [] 107 | self.lines = [] 108 | 109 | def parseMarker(self, l): 110 | self.markers.append(l) 111 | 112 | def parseLine(self, l): 113 | self.lines.append(l.strip('\n')) 114 | 115 | def getCode(self): 116 | """ Extract the executable Python code from the generator. 117 | """ 118 | # If the markers and lines all have the same prefix 119 | # (end-of-line comment chars, for example), 120 | # then remove it from all the lines. 121 | prefIn = commonPrefix(self.markers + self.lines) 122 | if prefIn: 123 | self.markers = [ l.replace(prefIn, '', 1) for l in self.markers ] 124 | self.lines = [ l.replace(prefIn, '', 1) for l in self.lines ] 125 | 126 | return reindentBlock(self.lines, '') 127 | 128 | def evaluate(self, cog, globals, fname='cog generator'): 129 | # figure out the right whitespace prefix for the output 130 | prefOut = whitePrefix(self.markers) 131 | 132 | intext = self.getCode() 133 | if not intext: 134 | return '' 135 | 136 | # In Python 2.2, the last line has to end in a newline. 137 | intext = "import cog\n" + intext + "\n" 138 | code = compile(intext, str(fname), 'exec') 139 | 140 | # Make sure the "cog" module has our state. 141 | cog.cogmodule.msg = self.msg 142 | cog.cogmodule.out = self.out 143 | cog.cogmodule.outl = self.outl 144 | cog.cogmodule.error = self.error 145 | 146 | self.outstring = '' 147 | eval(code, globals) 148 | 149 | # We need to make sure that the last line in the output 150 | # ends with a newline, or it will be joined to the 151 | # end-output line, ruining cog's idempotency. 152 | if self.outstring and self.outstring[-1] != '\n': 153 | self.outstring += '\n' 154 | 155 | return reindentBlock(self.outstring, prefOut) 156 | 157 | def msg(self, s): 158 | self.prout("Message: "+s) 159 | 160 | def out(self, sOut='', dedent=False, trimblanklines=False): 161 | """ The cog.out function. 162 | """ 163 | if trimblanklines and ('\n' in sOut): 164 | lines = sOut.split('\n') 165 | if lines[0].strip() == '': 166 | del lines[0] 167 | if lines and lines[-1].strip() == '': 168 | del lines[-1] 169 | sOut = '\n'.join(lines)+'\n' 170 | if dedent: 171 | sOut = reindentBlock(sOut) 172 | self.outstring += sOut 173 | 174 | def outl(self, sOut='', **kw): 175 | """ The cog.outl function. 176 | """ 177 | self.out(sOut, **kw) 178 | self.out('\n') 179 | 180 | def error(self, msg='Error raised by cog generator.'): 181 | """ The cog.error function. 182 | Instead of raising standard python errors, cog generators can use 183 | this function. It will display the error without a scary Python 184 | traceback. 185 | """ 186 | raise CogGeneratedError(msg) 187 | 188 | 189 | class NumberedFileReader: 190 | """ A decorator for files that counts the readline()'s called. 191 | """ 192 | def __init__(self, f): 193 | self.f = f 194 | self.n = 0 195 | 196 | def readline(self): 197 | l = self.f.readline() 198 | if l: 199 | self.n += 1 200 | return l 201 | 202 | def linenumber(self): 203 | return self.n 204 | 205 | 206 | class CogOptions: 207 | """ Options for a run of cog. 208 | """ 209 | def __init__(self): 210 | # Defaults for argument values. 211 | self.args = [] 212 | self.includePath = [] 213 | self.defines = {} 214 | self.bShowVersion = False 215 | self.sMakeWritableCmd = None 216 | self.bReplace = False 217 | self.bNoGenerate = False 218 | self.sOutputName = None 219 | self.bWarnEmpty = False 220 | self.bHashOutput = False 221 | self.bDeleteCode = False 222 | self.bEofCanBeEnd = False 223 | self.bQuiet = False 224 | self.sSuffix = None 225 | self.bNewlines = False 226 | self.sBeginSpec = '[[[cog' 227 | self.sEndSpec = ']]]' 228 | self.sEndOutput = '[[[end]]]' 229 | self.sEncoding = "utf-8" 230 | 231 | def __eq__(self, other): 232 | """ Comparison operator for tests to use. 233 | """ 234 | return self.__dict__ == other.__dict__ 235 | 236 | def clone(self): 237 | """ Make a clone of these options, for further refinement. 238 | """ 239 | return copy.deepcopy(self) 240 | 241 | def addToIncludePath(self, dirs): 242 | """ Add directories to the include path. 243 | """ 244 | dirs = dirs.split(os.pathsep) 245 | self.includePath.extend(dirs) 246 | 247 | def parseArgs(self, argv): 248 | # Parse the command line arguments. 249 | try: 250 | opts, self.args = getopt.getopt(argv, 'cdD:eI:n:o:qrs:Uvw:xz', 251 | ['begin-spec=', 'end-spec=', 252 | 'end-output=']) 253 | except getopt.error as msg: 254 | raise CogUsageError(msg) 255 | 256 | # Handle the command line arguments. 257 | for o, a in opts: 258 | if o == '-c': 259 | self.bHashOutput = True 260 | elif o == '-d': 261 | self.bDeleteCode = True 262 | elif o == '-D': 263 | if a.count('=') < 1: 264 | raise CogUsageError("-D takes a name=value argument") 265 | name, value = a.split('=', 1) 266 | self.defines[name] = value 267 | elif o == '-e': 268 | self.bWarnEmpty = True 269 | elif o == '-I': 270 | self.addToIncludePath(a) 271 | elif o == '-n': 272 | self.sEncoding = a 273 | elif o == '-o': 274 | self.sOutputName = a 275 | elif o == '-q': 276 | self.bQuiet = True 277 | elif o == '-r': 278 | self.bReplace = True 279 | elif o == '-s': 280 | self.sSuffix = a 281 | elif o == '-U': 282 | self.bNewlines = True 283 | elif o == '-v': 284 | self.bShowVersion = True 285 | elif o == '-w': 286 | self.sMakeWritableCmd = a 287 | elif o == '-x': 288 | self.bNoGenerate = True 289 | elif o == '-z': 290 | self.bEofCanBeEnd = True 291 | elif o == '--begin-spec': 292 | self.sBeginSpec = a 293 | elif o == '--end-spec': 294 | self.sEndSpec = a 295 | elif o == '--end-output': 296 | self.sEndOutput = a 297 | else: 298 | # Since getopt.getopt is given a list of possible flags, 299 | # this is an internal error. 300 | raise CogInternalError("Don't understand argument %s" % o) 301 | 302 | def validate(self): 303 | """ Does nothing if everything is OK, raises CogError's if it's not. 304 | """ 305 | if self.bReplace and self.bDeleteCode: 306 | raise CogUsageError("Can't use -d with -r (or you would delete all your source!)") 307 | 308 | if self.bReplace and self.sOutputName: 309 | raise CogUsageError("Can't use -o with -r (they are opposites)") 310 | 311 | 312 | class Cog(Redirectable): 313 | """ The Cog engine. 314 | """ 315 | def __init__(self): 316 | Redirectable.__init__(self) 317 | self.options = CogOptions() 318 | self._fixEndOutputPatterns() 319 | self.installCogModule() 320 | 321 | def _fixEndOutputPatterns(self): 322 | end_output = re.escape(self.options.sEndOutput) 323 | self.reEndOutput = re.compile(end_output + r'(?P *\(checksum: (?P[a-f0-9]+)\))') 324 | self.sEndFormat = self.options.sEndOutput + ' (checksum: %s)' 325 | 326 | def showWarning(self, msg): 327 | self.prout("Warning: "+msg) 328 | 329 | def isBeginSpecLine(self, s): 330 | return self.options.sBeginSpec in s 331 | 332 | def isEndSpecLine(self, s): 333 | return self.options.sEndSpec in s and not self.isEndOutputLine(s) 334 | 335 | def isEndOutputLine(self, s): 336 | return self.options.sEndOutput in s 337 | 338 | def installCogModule(self): 339 | """ Magic mumbo-jumbo so that imported Python modules 340 | can say "import cog" and get our state. 341 | """ 342 | self.cogmodule = imp.new_module('cog') 343 | self.cogmodule.path = [] 344 | sys.modules['cog'] = self.cogmodule 345 | 346 | def openOutputFile(self, fname): 347 | """ Open an output file, taking all the details into account. 348 | """ 349 | opts = {} 350 | mode = "w" 351 | if PY3: 352 | opts['encoding'] = self.options.sEncoding 353 | if self.options.bNewlines: 354 | if PY3: 355 | opts['newline'] = "\n" 356 | else: 357 | mode = "wb" 358 | return open(fname, mode, **opts) 359 | 360 | def openInputFile(self, fname): 361 | """ Open an input file. """ 362 | if fname == "-": 363 | return sys.stdin 364 | else: 365 | opts = {} 366 | if PY3: 367 | opts['encoding'] = self.options.sEncoding 368 | return open(fname, "r", **opts) 369 | 370 | def processFile(self, fIn, fOut, fname=None, globals=None): 371 | """ Process an input file object to an output file object. 372 | fIn and fOut can be file objects, or file names. 373 | """ 374 | 375 | sFileIn = fname or '' 376 | sFileOut = fname or '' 377 | fInToClose = fOutToClose = None 378 | # Convert filenames to files. 379 | if isinstance(fIn, string_types): 380 | # Open the input file. 381 | sFileIn = fIn 382 | fIn = fInToClose = self.openInputFile(fIn) 383 | if isinstance(fOut, string_types): 384 | # Open the output file. 385 | sFileOut = fOut 386 | fOut = fOutToClose = self.openOutputFile(fOut) 387 | 388 | try: 389 | fIn = NumberedFileReader(fIn) 390 | 391 | bSawCog = False 392 | 393 | self.cogmodule.inFile = sFileIn 394 | self.cogmodule.outFile = sFileOut 395 | 396 | # The globals dict we'll use for this file. 397 | if globals is None: 398 | globals = {} 399 | 400 | # If there are any global defines, put them in the globals. 401 | globals.update(self.options.defines) 402 | 403 | # loop over generator chunks 404 | l = fIn.readline() 405 | while l: 406 | # Find the next spec begin 407 | while l and not self.isBeginSpecLine(l): 408 | if self.isEndSpecLine(l): 409 | raise CogError("Unexpected '%s'" % self.options.sEndSpec, 410 | file=sFileIn, line=fIn.linenumber()) 411 | if self.isEndOutputLine(l): 412 | raise CogError("Unexpected '%s'" % self.options.sEndOutput, 413 | file=sFileIn, line=fIn.linenumber()) 414 | fOut.write(l) 415 | l = fIn.readline() 416 | if not l: 417 | break 418 | if not self.options.bDeleteCode: 419 | fOut.write(l) 420 | 421 | # l is the begin spec 422 | gen = CogGenerator() 423 | gen.setOutput(stdout=self.stdout) 424 | gen.parseMarker(l) 425 | firstLineNum = fIn.linenumber() 426 | self.cogmodule.firstLineNum = firstLineNum 427 | 428 | # If the spec begin is also a spec end, then process the single 429 | # line of code inside. 430 | if self.isEndSpecLine(l): 431 | beg = l.find(self.options.sBeginSpec) 432 | end = l.find(self.options.sEndSpec) 433 | if beg > end: 434 | raise CogError("Cog code markers inverted", 435 | file=sFileIn, line=firstLineNum) 436 | else: 437 | sCode = l[beg+len(self.options.sBeginSpec):end].strip() 438 | gen.parseLine(sCode) 439 | else: 440 | # Deal with an ordinary code block. 441 | l = fIn.readline() 442 | 443 | # Get all the lines in the spec 444 | while l and not self.isEndSpecLine(l): 445 | if self.isBeginSpecLine(l): 446 | raise CogError("Unexpected '%s'" % self.options.sBeginSpec, 447 | file=sFileIn, line=fIn.linenumber()) 448 | if self.isEndOutputLine(l): 449 | raise CogError("Unexpected '%s'" % self.options.sEndOutput, 450 | file=sFileIn, line=fIn.linenumber()) 451 | if not self.options.bDeleteCode: 452 | fOut.write(l) 453 | gen.parseLine(l) 454 | l = fIn.readline() 455 | if not l: 456 | raise CogError( 457 | "Cog block begun but never ended.", 458 | file=sFileIn, line=firstLineNum) 459 | 460 | if not self.options.bDeleteCode: 461 | fOut.write(l) 462 | gen.parseMarker(l) 463 | 464 | l = fIn.readline() 465 | 466 | # Eat all the lines in the output section. While reading past 467 | # them, compute the md5 hash of the old output. 468 | previous = "" 469 | hasher = hashlib.md5() 470 | while l and not self.isEndOutputLine(l): 471 | if self.isBeginSpecLine(l): 472 | raise CogError("Unexpected '%s'" % self.options.sBeginSpec, 473 | file=sFileIn, line=fIn.linenumber()) 474 | if self.isEndSpecLine(l): 475 | raise CogError("Unexpected '%s'" % self.options.sEndSpec, 476 | file=sFileIn, line=fIn.linenumber()) 477 | previous += l 478 | hasher.update(to_bytes(l)) 479 | l = fIn.readline() 480 | curHash = hasher.hexdigest() 481 | 482 | if not l and not self.options.bEofCanBeEnd: 483 | # We reached end of file before we found the end output line. 484 | raise CogError("Missing '%s' before end of file." % self.options.sEndOutput, 485 | file=sFileIn, line=fIn.linenumber()) 486 | 487 | # Make the previous output available to the current code 488 | self.cogmodule.previous = previous 489 | 490 | # Write the output of the spec to be the new output if we're 491 | # supposed to generate code. 492 | hasher = hashlib.md5() 493 | if not self.options.bNoGenerate: 494 | sFile = "%s+%d" % (sFileIn, firstLineNum) 495 | sGen = gen.evaluate(cog=self, globals=globals, fname=sFile) 496 | sGen = self.suffixLines(sGen) 497 | hasher.update(to_bytes(sGen)) 498 | fOut.write(sGen) 499 | newHash = hasher.hexdigest() 500 | 501 | bSawCog = True 502 | 503 | # Write the ending output line 504 | hashMatch = self.reEndOutput.search(l) 505 | if self.options.bHashOutput: 506 | if hashMatch: 507 | oldHash = hashMatch.groupdict()['hash'] 508 | if oldHash != curHash: 509 | raise CogError("Output has been edited! Delete old checksum to unprotect.", 510 | file=sFileIn, line=fIn.linenumber()) 511 | # Create a new end line with the correct hash. 512 | endpieces = l.split(hashMatch.group(0), 1) 513 | else: 514 | # There was no old hash, but we want a new hash. 515 | endpieces = l.split(self.options.sEndOutput, 1) 516 | l = (self.sEndFormat % newHash).join(endpieces) 517 | else: 518 | # We don't want hashes output, so if there was one, get rid of 519 | # it. 520 | if hashMatch: 521 | l = l.replace(hashMatch.groupdict()['hashsect'], '', 1) 522 | 523 | if not self.options.bDeleteCode: 524 | fOut.write(l) 525 | l = fIn.readline() 526 | 527 | if not bSawCog and self.options.bWarnEmpty: 528 | self.showWarning("no cog code found in %s" % sFileIn) 529 | finally: 530 | if fInToClose: 531 | fInToClose.close() 532 | if fOutToClose: 533 | fOutToClose.close() 534 | 535 | 536 | # A regex for non-empty lines, used by suffixLines. 537 | reNonEmptyLines = re.compile("^\s*\S+.*$", re.MULTILINE) 538 | 539 | def suffixLines(self, text): 540 | """ Add suffixes to the lines in text, if our options desire it. 541 | text is many lines, as a single string. 542 | """ 543 | if self.options.sSuffix: 544 | # Find all non-blank lines, and add the suffix to the end. 545 | repl = r"\g<0>" + self.options.sSuffix.replace('\\', '\\\\') 546 | text = self.reNonEmptyLines.sub(repl, text) 547 | return text 548 | 549 | def processString(self, sInput, fname=None): 550 | """ Process sInput as the text to cog. 551 | Return the cogged output as a string. 552 | """ 553 | fOld = StringIO(sInput) 554 | fNew = StringIO() 555 | self.processFile(fOld, fNew, fname=fname) 556 | return fNew.getvalue() 557 | 558 | def replaceFile(self, sOldPath, sNewText): 559 | """ Replace file sOldPath with the contents sNewText 560 | """ 561 | if not os.access(sOldPath, os.W_OK): 562 | # Need to ensure we can write. 563 | if self.options.sMakeWritableCmd: 564 | # Use an external command to make the file writable. 565 | cmd = self.options.sMakeWritableCmd.replace('%s', sOldPath) 566 | self.stdout.write(os.popen(cmd).read()) 567 | if not os.access(sOldPath, os.W_OK): 568 | raise CogError("Couldn't make %s writable" % sOldPath) 569 | else: 570 | # Can't write! 571 | raise CogError("Can't overwrite %s" % sOldPath) 572 | f = self.openOutputFile(sOldPath) 573 | f.write(sNewText) 574 | f.close() 575 | 576 | def saveIncludePath(self): 577 | self.savedInclude = self.options.includePath[:] 578 | self.savedSysPath = sys.path[:] 579 | 580 | def restoreIncludePath(self): 581 | self.options.includePath = self.savedInclude 582 | self.cogmodule.path = self.options.includePath 583 | sys.path = self.savedSysPath 584 | 585 | def addToIncludePath(self, includePath): 586 | self.cogmodule.path.extend(includePath) 587 | sys.path.extend(includePath) 588 | 589 | def processOneFile(self, sFile): 590 | """ Process one filename through cog. 591 | """ 592 | 593 | self.saveIncludePath() 594 | 595 | try: 596 | self.addToIncludePath(self.options.includePath) 597 | # Since we know where the input file came from, 598 | # push its directory onto the include path. 599 | self.addToIncludePath([os.path.dirname(sFile)]) 600 | 601 | # How we process the file depends on where the output is going. 602 | if self.options.sOutputName: 603 | self.processFile(sFile, self.options.sOutputName, sFile) 604 | elif self.options.bReplace: 605 | # We want to replace the cog file with the output, 606 | # but only if they differ. 607 | bNeedNewline = False 608 | if not self.options.bQuiet: 609 | self.prout("Cogging %s" % sFile, end="") 610 | bNeedNewline = True 611 | 612 | try: 613 | fOldFile = self.openInputFile(sFile) 614 | sOldText = fOldFile.read() 615 | fOldFile.close() 616 | sNewText = self.processString(sOldText, fname=sFile) 617 | if sOldText != sNewText: 618 | if self.options.bQuiet: 619 | self.prout("Cogging %s" % sFile, end="") 620 | self.prout(" (changed)") 621 | bNeedNewline = False 622 | self.replaceFile(sFile, sNewText) 623 | finally: 624 | # The try-finally block is so we can print a partial line 625 | # with the name of the file, and print (changed) on the 626 | # same line, but also make sure to break the line before 627 | # any traceback. 628 | if bNeedNewline: 629 | self.prout("") 630 | else: 631 | self.processFile(sFile, self.stdout, sFile) 632 | finally: 633 | self.restoreIncludePath() 634 | 635 | def processWildcards(self, sFile ): 636 | for sMatchingFile in glob.glob( sFile ): 637 | self.processOneFile( sMatchingFile ) 638 | 639 | def processFileList(self, sFileList): 640 | """ Process the files in a file list. 641 | """ 642 | flist = self.openInputFile(sFileList) 643 | lines = flist.readlines() 644 | flist.close() 645 | for l in lines: 646 | # Use shlex to parse the line like a shell. 647 | lex = shlex.shlex(l, posix=True) 648 | lex.whitespace_split = True 649 | lex.commenters = '#' 650 | # No escapes, so that backslash can be part of the path 651 | lex.escape = '' 652 | args = list(lex) 653 | if args: 654 | self.processArguments(args) 655 | 656 | def processArguments(self, args): 657 | """ Process one command-line. 658 | """ 659 | saved_options = self.options 660 | self.options = self.options.clone() 661 | 662 | self.options.parseArgs(args[1:]) 663 | self.options.validate() 664 | 665 | if args[0][0] == '@': 666 | if self.options.sOutputName: 667 | raise CogUsageError("Can't use -o with @file") 668 | self.processFileList(args[0][1:]) 669 | else: 670 | self.processWildcards(args[0]) 671 | 672 | self.options = saved_options 673 | 674 | def callableMain(self, argv): 675 | """ All of command-line cog, but in a callable form. 676 | This is used by main. 677 | argv is the equivalent of sys.argv. 678 | """ 679 | argv = argv[:] 680 | argv0 = argv.pop(0) 681 | 682 | # Provide help if asked for anywhere in the command line. 683 | if '-?' in argv or '-h' in argv: 684 | self.prerr(usage, end="") 685 | return 686 | 687 | self.options.parseArgs(argv) 688 | self.options.validate() 689 | self._fixEndOutputPatterns() 690 | 691 | if self.options.bShowVersion: 692 | self.prout("Cog version %s" % __version__) 693 | return 694 | 695 | if self.options.args: 696 | for a in self.options.args: 697 | self.processArguments([a]) 698 | else: 699 | raise CogUsageError("No files to process") 700 | 701 | def main(self, argv): 702 | """ Handle the command-line execution for cog. 703 | """ 704 | 705 | try: 706 | self.callableMain(argv) 707 | return 0 708 | except CogUsageError as err: 709 | self.prerr(err) 710 | self.prerr("(for help use -?)") 711 | return 2 712 | except CogGeneratedError as err: 713 | self.prerr("Error: %s" % err) 714 | return 3 715 | except CogError as err: 716 | self.prerr(err) 717 | return 1 718 | except: 719 | traceback.print_exc(None, self.stderr) 720 | return 1 721 | 722 | # History: 723 | # 20040210: First public version. 724 | # 20040220: Text preceding the start and end marker are removed from Python lines. 725 | # -v option on the command line shows the version. 726 | # 20040311: Make sure the last line of output is properly ended with a newline. 727 | # 20040605: Fixed some blank line handling in cog. 728 | # Fixed problems with assigning to xml elements in handyxml. 729 | # 20040621: Changed all line-ends to LF from CRLF. 730 | # 20041002: Refactor some option handling to simplify unittesting the options. 731 | # 20041118: cog.out and cog.outl have optional string arguments. 732 | # 20041119: File names weren't being properly passed around for warnings, etc. 733 | # 20041122: Added cog.firstLineNum: a property with the line number of the [[[cog line. 734 | # Added cog.inFile and cog.outFile: the names of the input and output file. 735 | # 20041218: Single-line cog generators, with start marker and end marker on 736 | # the same line. 737 | # 20041230: Keep a single globals dict for all the code fragments in a single 738 | # file so they can share state. 739 | # 20050206: Added the -x switch to remove all generated output. 740 | # 20050218: Now code can be on the marker lines as well. 741 | # 20050219: Added -c switch to checksum the output so that edits can be 742 | # detected before they are obliterated. 743 | # 20050521: Added cog.error, contributed by Alexander Belchenko. 744 | # 20050720: Added code deletion and settable globals contributed by Blake Winton. 745 | # 20050724: Many tweaks to improve code coverage. 746 | # 20050726: Error messages are now printed with no traceback. 747 | # Code can no longer appear on the marker lines, 748 | # except for single-line style. 749 | # -z allows omission of the [[[end]]] marker, and it will be assumed 750 | # at the end of the file. 751 | # 20050729: Refactor option parsing into a separate class, in preparation for 752 | # future features. 753 | # 20050805: The cogmodule.path wasn't being properly maintained. 754 | # 20050808: Added the -D option to define a global value. 755 | # 20050810: The %s in the -w command is dealt with more robustly. 756 | # Added the -s option to suffix output lines with a marker. 757 | # 20050817: Now @files can have arguments on each line to change the cog's 758 | # behavior for that line. 759 | # 20051006: Version 2.0 760 | # 20080521: -U options lets you create Unix newlines on Windows. Thanks, 761 | # Alexander Belchenko. 762 | # 20080522: It's now ok to have -d with output to stdout, and now we validate 763 | # the args after each line of an @file. 764 | # 20090520: Use hashlib where it's available, to avoid a warning. 765 | # Use the builtin compile() instead of compiler, for Jython. 766 | # Explicitly close files we opened, Jython likes this. 767 | # 20120205: Port to Python 3. Lowest supported version is 2.6. 768 | # 20150104: --begin-spec, --end-spec, and --end-output options added by Doug 769 | # Hellmann. 770 | # 20150104: -n ENCODING option added by Petr Gladkiy. 771 | # 20150105: -q quiet option added by Hugh Perkins: only shows changed files 772 | -------------------------------------------------------------------------------- /thirdparty/cogapp/cogapp/makefiles.py: -------------------------------------------------------------------------------- 1 | """ Dictionary-to-filetree functions, to create test files for testing. 2 | http://nedbatchelder.com/code/cog 3 | 4 | Copyright 2004-2012, Ned Batchelder. 5 | """ 6 | 7 | from __future__ import absolute_import 8 | import os.path 9 | from .whiteutils import reindentBlock 10 | from .backward import string_types, bytes_types 11 | 12 | __version__ = '1.0.20040126' 13 | __all__ = ['makeFiles', 'removeFiles'] 14 | 15 | def makeFiles(d, basedir='.'): 16 | """ Create files from the dictionary `d`, in the directory named by `basedir`. 17 | """ 18 | for name, contents in d.items(): 19 | child = os.path.join(basedir, name) 20 | if isinstance(contents, string_types): 21 | mode = 'w' 22 | if isinstance(contents, bytes_types): 23 | mode += "b" 24 | f = open(child, mode) 25 | contents = reindentBlock(contents) 26 | f.write(contents) 27 | f.close() 28 | else: 29 | if not os.path.exists(child): 30 | os.mkdir(child) 31 | makeFiles(contents, child) 32 | 33 | def removeFiles(d, basedir='.'): 34 | """ Remove the files created by makeFiles. 35 | Directories are removed if they are empty. 36 | """ 37 | for name, contents in d.items(): 38 | child = os.path.join(basedir, name) 39 | if isinstance(contents, string_types): 40 | os.remove(child) 41 | else: 42 | removeFiles(contents, child) 43 | if not os.listdir(child): 44 | os.rmdir(child) 45 | 46 | if __name__ == '__main__': #pragma: no cover 47 | # Try it a little. 48 | d = { 49 | 'test_makefiles': { 50 | 'hey.txt': """\ 51 | This is hey.txt. 52 | It's very simple. 53 | """, 54 | 'subdir': { 55 | 'fooey': """\ 56 | # Fooey 57 | Kablooey 58 | Ew. 59 | """ 60 | } 61 | } 62 | } 63 | makeFiles(d) 64 | -------------------------------------------------------------------------------- /thirdparty/cogapp/cogapp/test_cogapp.py: -------------------------------------------------------------------------------- 1 | """ Test cogapp. 2 | http://nedbatchelder.com/code/cog 3 | 4 | Copyright 2004-2015, Ned Batchelder. 5 | """ 6 | 7 | from __future__ import absolute_import 8 | 9 | import os, os.path, random, re, shutil, stat, sys, tempfile 10 | 11 | # Use unittest2 if it's available, otherwise unittest. This gives us 12 | # back-ported features for 2.6. 13 | try: 14 | import unittest2 as unittest 15 | except ImportError: 16 | import unittest 17 | 18 | from .backward import StringIO, to_bytes, b 19 | from .cogapp import Cog, CogOptions, CogGenerator 20 | from .cogapp import CogError, CogUsageError, CogGeneratedError 21 | from .cogapp import usage, __version__ 22 | from .whiteutils import reindentBlock 23 | from .makefiles import * 24 | 25 | 26 | TestCase = unittest.TestCase 27 | 28 | 29 | class CogTestsInMemory(TestCase): 30 | """ Test cases for cogapp.Cog() 31 | """ 32 | 33 | def testNoCog(self): 34 | strings = [ 35 | '', 36 | ' ', 37 | ' \t \t \tx', 38 | 'hello', 39 | 'the cat\nin the\nhat.', 40 | 'Horton\n\tHears A\n\t\tWho' 41 | ] 42 | for s in strings: 43 | self.assertEqual(Cog().processString(s), s) 44 | 45 | def testSimple(self): 46 | infile = """\ 47 | Some text. 48 | //[[[cog 49 | import cog 50 | cog.outl("This is line one\\n") 51 | cog.outl("This is line two") 52 | //]]] 53 | gobbledegook. 54 | //[[[end]]] 55 | epilogue. 56 | """ 57 | 58 | outfile = """\ 59 | Some text. 60 | //[[[cog 61 | import cog 62 | cog.outl("This is line one\\n") 63 | cog.outl("This is line two") 64 | //]]] 65 | This is line one 66 | 67 | This is line two 68 | //[[[end]]] 69 | epilogue. 70 | """ 71 | 72 | self.assertEqual(Cog().processString(infile), outfile) 73 | 74 | def testEmptyCog(self): 75 | # The cog clause can be totally empty. Not sure why you'd want it, 76 | # but it works. 77 | infile = """\ 78 | hello 79 | //[[[cog 80 | //]]] 81 | //[[[end]]] 82 | goodbye 83 | """ 84 | 85 | infile = reindentBlock(infile) 86 | self.assertEqual(Cog().processString(infile), infile) 87 | 88 | def testMultipleCogs(self): 89 | # One file can have many cog chunks, even abutting each other. 90 | infile = """\ 91 | //[[[cog 92 | cog.out("chunk1") 93 | //]]] 94 | chunk1 95 | //[[[end]]] 96 | //[[[cog 97 | cog.out("chunk2") 98 | //]]] 99 | chunk2 100 | //[[[end]]] 101 | between chunks 102 | //[[[cog 103 | cog.out("chunk3") 104 | //]]] 105 | chunk3 106 | //[[[end]]] 107 | """ 108 | 109 | infile = reindentBlock(infile) 110 | self.assertEqual(Cog().processString(infile), infile) 111 | 112 | def testTrimBlankLines(self): 113 | infile = """\ 114 | //[[[cog 115 | cog.out("This is line one\\n", trimblanklines=True) 116 | cog.out(''' 117 | This is line two 118 | ''', dedent=True, trimblanklines=True) 119 | cog.outl("This is line three", trimblanklines=True) 120 | //]]] 121 | This is line one 122 | This is line two 123 | This is line three 124 | //[[[end]]] 125 | """ 126 | 127 | infile = reindentBlock(infile) 128 | self.assertEqual(Cog().processString(infile), infile) 129 | 130 | def testTrimEmptyBlankLines(self): 131 | infile = """\ 132 | //[[[cog 133 | cog.out("This is line one\\n", trimblanklines=True) 134 | cog.out(''' 135 | This is line two 136 | ''', dedent=True, trimblanklines=True) 137 | cog.out('', dedent=True, trimblanklines=True) 138 | cog.outl("This is line three", trimblanklines=True) 139 | //]]] 140 | This is line one 141 | This is line two 142 | This is line three 143 | //[[[end]]] 144 | """ 145 | 146 | infile = reindentBlock(infile) 147 | self.assertEqual(Cog().processString(infile), infile) 148 | 149 | def test22EndOfLine(self): 150 | # In Python 2.2, this cog file was not parsing because the 151 | # last line is indented but didn't end with a newline. 152 | infile = """\ 153 | //[[[cog 154 | import cog 155 | for i in range(3): 156 | cog.out("%d\\n" % i) 157 | //]]] 158 | 0 159 | 1 160 | 2 161 | //[[[end]]] 162 | """ 163 | 164 | infile = reindentBlock(infile) 165 | self.assertEqual(Cog().processString(infile), infile) 166 | 167 | def testIndentedCode(self): 168 | infile = """\ 169 | first line 170 | [[[cog 171 | import cog 172 | for i in range(3): 173 | cog.out("xx%d\\n" % i) 174 | ]]] 175 | xx0 176 | xx1 177 | xx2 178 | [[[end]]] 179 | last line 180 | """ 181 | 182 | infile = reindentBlock(infile) 183 | self.assertEqual(Cog().processString(infile), infile) 184 | 185 | def testPrefixedCode(self): 186 | infile = """\ 187 | --[[[cog 188 | --import cog 189 | --for i in range(3): 190 | -- cog.out("xx%d\\n" % i) 191 | --]]] 192 | xx0 193 | xx1 194 | xx2 195 | --[[[end]]] 196 | """ 197 | 198 | infile = reindentBlock(infile) 199 | self.assertEqual(Cog().processString(infile), infile) 200 | 201 | def testPrefixedIndentedCode(self): 202 | infile = """\ 203 | prologue 204 | --[[[cog 205 | -- import cog 206 | -- for i in range(3): 207 | -- cog.out("xy%d\\n" % i) 208 | --]]] 209 | xy0 210 | xy1 211 | xy2 212 | --[[[end]]] 213 | """ 214 | 215 | infile = reindentBlock(infile) 216 | self.assertEqual(Cog().processString(infile), infile) 217 | 218 | def testBogusPrefixMatch(self): 219 | infile = """\ 220 | prologue 221 | #[[[cog 222 | import cog 223 | # This comment should not be clobbered by removing the pound sign. 224 | for i in range(3): 225 | cog.out("xy%d\\n" % i) 226 | #]]] 227 | xy0 228 | xy1 229 | xy2 230 | #[[[end]]] 231 | """ 232 | 233 | infile = reindentBlock(infile) 234 | self.assertEqual(Cog().processString(infile), infile) 235 | 236 | def testNoFinalNewline(self): 237 | # If the cog'ed output has no final newline, 238 | # it shouldn't eat up the cog terminator. 239 | infile = """\ 240 | prologue 241 | [[[cog 242 | import cog 243 | for i in range(3): 244 | cog.out("%d" % i) 245 | ]]] 246 | 012 247 | [[[end]]] 248 | epilogue 249 | """ 250 | 251 | infile = reindentBlock(infile) 252 | self.assertEqual(Cog().processString(infile), infile) 253 | 254 | def testNoOutputAtAll(self): 255 | # If there is absolutely no cog output, that's ok. 256 | infile = """\ 257 | prologue 258 | [[[cog 259 | i = 1 260 | ]]] 261 | [[[end]]] 262 | epilogue 263 | """ 264 | 265 | infile = reindentBlock(infile) 266 | self.assertEqual(Cog().processString(infile), infile) 267 | 268 | def testPurelyBlankLine(self): 269 | # If there is a blank line in the cog code with no whitespace 270 | # prefix, that should be OK. 271 | 272 | infile = """\ 273 | prologue 274 | [[[cog 275 | import sys 276 | cog.out("Hello") 277 | $ 278 | cog.out("There") 279 | ]]] 280 | HelloThere 281 | [[[end]]] 282 | epilogue 283 | """ 284 | 285 | infile = reindentBlock(infile.replace('$', '')) 286 | self.assertEqual(Cog().processString(infile), infile) 287 | 288 | def testEmptyOutl(self): 289 | # Alexander Belchenko suggested the string argument to outl should 290 | # be optional. Does it work? 291 | 292 | infile = """\ 293 | prologue 294 | [[[cog 295 | cog.outl("x") 296 | cog.outl() 297 | cog.outl("y") 298 | cog.outl(trimblanklines=True) 299 | cog.outl("z") 300 | ]]] 301 | x 302 | 303 | y 304 | 305 | z 306 | [[[end]]] 307 | epilogue 308 | """ 309 | 310 | infile = reindentBlock(infile) 311 | self.assertEqual(Cog().processString(infile), infile) 312 | 313 | def testFirstLineNum(self): 314 | infile = """\ 315 | fooey 316 | [[[cog 317 | cog.outl("started at line number %d" % cog.firstLineNum) 318 | ]]] 319 | started at line number 2 320 | [[[end]]] 321 | blah blah 322 | [[[cog 323 | cog.outl("and again at line %d" % cog.firstLineNum) 324 | ]]] 325 | and again at line 8 326 | [[[end]]] 327 | """ 328 | 329 | infile = reindentBlock(infile) 330 | self.assertEqual(Cog().processString(infile), infile) 331 | 332 | def testCompactOneLineCode(self): 333 | infile = """\ 334 | first line 335 | hey: [[[cog cog.outl("hello %d" % (3*3*3*3)) ]]] looky! 336 | get rid of this! 337 | [[[end]]] 338 | last line 339 | """ 340 | 341 | outfile = """\ 342 | first line 343 | hey: [[[cog cog.outl("hello %d" % (3*3*3*3)) ]]] looky! 344 | hello 81 345 | [[[end]]] 346 | last line 347 | """ 348 | 349 | infile = reindentBlock(infile) 350 | self.assertEqual(Cog().processString(infile), reindentBlock(outfile)) 351 | 352 | def testInsideOutCompact(self): 353 | infile = """\ 354 | first line 355 | hey?: ]]] what is this? [[[cog strange! 356 | get rid of this! 357 | [[[end]]] 358 | last line 359 | """ 360 | with self.assertRaisesRegexp(CogError, r"infile.txt\(2\): Cog code markers inverted"): 361 | Cog().processString(reindentBlock(infile), "infile.txt") 362 | 363 | def testSharingGlobals(self): 364 | infile = """\ 365 | first line 366 | hey: [[[cog s="hey there" ]]] looky! 367 | [[[end]]] 368 | more literal junk. 369 | [[[cog cog.outl(s) ]]] 370 | [[[end]]] 371 | last line 372 | """ 373 | 374 | outfile = """\ 375 | first line 376 | hey: [[[cog s="hey there" ]]] looky! 377 | [[[end]]] 378 | more literal junk. 379 | [[[cog cog.outl(s) ]]] 380 | hey there 381 | [[[end]]] 382 | last line 383 | """ 384 | 385 | infile = reindentBlock(infile) 386 | self.assertEqual(Cog().processString(infile), reindentBlock(outfile)) 387 | 388 | def testAssertInCogCode(self): 389 | # Check that we can test assertions in cog code in the test framework. 390 | infile = """\ 391 | [[[cog 392 | assert 1 == 2, "Oops" 393 | ]]] 394 | [[[end]]] 395 | """ 396 | infile = reindentBlock(infile) 397 | with self.assertRaisesRegexp(AssertionError, "Oops"): 398 | Cog().processString(infile) 399 | 400 | def testCogPrevious(self): 401 | # Check that we can access the previous run's output. 402 | infile = """\ 403 | [[[cog 404 | assert cog.previous == "Hello there!\\n", "WTF??" 405 | cog.out(cog.previous) 406 | cog.outl("Ran again!") 407 | ]]] 408 | Hello there! 409 | [[[end]]] 410 | """ 411 | 412 | outfile = """\ 413 | [[[cog 414 | assert cog.previous == "Hello there!\\n", "WTF??" 415 | cog.out(cog.previous) 416 | cog.outl("Ran again!") 417 | ]]] 418 | Hello there! 419 | Ran again! 420 | [[[end]]] 421 | """ 422 | 423 | infile = reindentBlock(infile) 424 | self.assertEqual(Cog().processString(infile), reindentBlock(outfile)) 425 | 426 | 427 | class CogOptionsTests(TestCase): 428 | """ Test the CogOptions class. 429 | """ 430 | 431 | def testEquality(self): 432 | o = CogOptions() 433 | p = CogOptions() 434 | self.assertEqual(o, p) 435 | o.parseArgs(['-r']) 436 | self.assertNotEqual(o, p) 437 | p.parseArgs(['-r']) 438 | self.assertEqual(o, p) 439 | 440 | def testCloning(self): 441 | o = CogOptions() 442 | o.parseArgs(['-I', 'fooey', '-I', 'booey', '-s', ' /*x*/']) 443 | p = o.clone() 444 | self.assertEqual(o, p) 445 | p.parseArgs(['-I', 'huey', '-D', 'foo=quux']) 446 | self.assertNotEqual(o, p) 447 | q = CogOptions() 448 | q.parseArgs(['-I', 'fooey', '-I', 'booey', '-s', ' /*x*/', '-I', 'huey', '-D', 'foo=quux']) 449 | self.assertEqual(p, q) 450 | 451 | def testCombiningFlags(self): 452 | # Single-character flags can be combined. 453 | o = CogOptions() 454 | o.parseArgs(['-e', '-r', '-z']) 455 | p = CogOptions() 456 | p.parseArgs(['-erz']) 457 | self.assertEqual(o, p) 458 | 459 | 460 | class FileStructureTests(TestCase): 461 | """ Test cases to check that we're properly strict about the structure 462 | of files. 463 | """ 464 | 465 | def isBad(self, infile, msg=None): 466 | infile = reindentBlock(infile) 467 | with self.assertRaisesRegexp(CogError, re.escape(msg)): 468 | Cog().processString(infile, 'infile.txt') 469 | 470 | def testBeginNoEnd(self): 471 | infile = """\ 472 | Fooey 473 | #[[[cog 474 | cog.outl('hello') 475 | """ 476 | self.isBad(infile, "infile.txt(2): Cog block begun but never ended.") 477 | 478 | def testNoEoo(self): 479 | infile = """\ 480 | Fooey 481 | #[[[cog 482 | cog.outl('hello') 483 | #]]] 484 | """ 485 | self.isBad(infile, "infile.txt(4): Missing '[[[end]]]' before end of file.") 486 | 487 | infile2 = """\ 488 | Fooey 489 | #[[[cog 490 | cog.outl('hello') 491 | #]]] 492 | #[[[cog 493 | cog.outl('goodbye') 494 | #]]] 495 | """ 496 | self.isBad(infile2, "infile.txt(5): Unexpected '[[[cog'") 497 | 498 | def testStartWithEnd(self): 499 | infile = """\ 500 | #]]] 501 | """ 502 | self.isBad(infile, "infile.txt(1): Unexpected ']]]'") 503 | 504 | infile2 = """\ 505 | #[[[cog 506 | cog.outl('hello') 507 | #]]] 508 | #[[[end]]] 509 | #]]] 510 | """ 511 | self.isBad(infile2, "infile.txt(5): Unexpected ']]]'") 512 | 513 | def testStartWithEoo(self): 514 | infile = """\ 515 | #[[[end]]] 516 | """ 517 | self.isBad(infile, "infile.txt(1): Unexpected '[[[end]]]'") 518 | 519 | infile2 = """\ 520 | #[[[cog 521 | cog.outl('hello') 522 | #]]] 523 | #[[[end]]] 524 | #[[[end]]] 525 | """ 526 | self.isBad(infile2, "infile.txt(5): Unexpected '[[[end]]]'") 527 | 528 | def testNoEnd(self): 529 | infile = """\ 530 | #[[[cog 531 | cog.outl("hello") 532 | #[[[end]]] 533 | """ 534 | self.isBad(infile, "infile.txt(3): Unexpected '[[[end]]]'") 535 | 536 | infile2 = """\ 537 | #[[[cog 538 | cog.outl('hello') 539 | #]]] 540 | #[[[end]]] 541 | #[[[cog 542 | cog.outl("hello") 543 | #[[[end]]] 544 | """ 545 | self.isBad(infile2, "infile.txt(7): Unexpected '[[[end]]]'") 546 | 547 | def testTwoBegins(self): 548 | infile = """\ 549 | #[[[cog 550 | #[[[cog 551 | cog.outl("hello") 552 | #]]] 553 | #[[[end]]] 554 | """ 555 | self.isBad(infile, "infile.txt(2): Unexpected '[[[cog'") 556 | 557 | infile2 = """\ 558 | #[[[cog 559 | cog.outl("hello") 560 | #]]] 561 | #[[[end]]] 562 | #[[[cog 563 | #[[[cog 564 | cog.outl("hello") 565 | #]]] 566 | #[[[end]]] 567 | """ 568 | self.isBad(infile2, "infile.txt(6): Unexpected '[[[cog'") 569 | 570 | def testTwoEnds(self): 571 | infile = """\ 572 | #[[[cog 573 | cog.outl("hello") 574 | #]]] 575 | #]]] 576 | #[[[end]]] 577 | """ 578 | self.isBad(infile, "infile.txt(4): Unexpected ']]]'") 579 | 580 | infile2 = """\ 581 | #[[[cog 582 | cog.outl("hello") 583 | #]]] 584 | #[[[end]]] 585 | #[[[cog 586 | cog.outl("hello") 587 | #]]] 588 | #]]] 589 | #[[[end]]] 590 | """ 591 | self.isBad(infile2, "infile.txt(8): Unexpected ']]]'") 592 | 593 | 594 | class CogErrorTests(TestCase): 595 | """ Test cases for cog.error(). 596 | """ 597 | 598 | def testErrorMsg(self): 599 | infile = """\ 600 | [[[cog cog.error("This ain't right!")]]] 601 | [[[end]]] 602 | """ 603 | 604 | infile = reindentBlock(infile) 605 | with self.assertRaisesRegexp(CogGeneratedError, "This ain't right!"): 606 | Cog().processString(infile) 607 | 608 | def testErrorNoMsg(self): 609 | infile = """\ 610 | [[[cog cog.error()]]] 611 | [[[end]]] 612 | """ 613 | 614 | infile = reindentBlock(infile) 615 | with self.assertRaisesRegexp(CogGeneratedError, "Error raised by cog generator."): 616 | Cog().processString(infile) 617 | 618 | def testNoErrorIfErrorNotCalled(self): 619 | infile = """\ 620 | --[[[cog 621 | --import cog 622 | --for i in range(3): 623 | -- if i > 10: 624 | -- cog.error("Something is amiss!") 625 | -- cog.out("xx%d\\n" % i) 626 | --]]] 627 | xx0 628 | xx1 629 | xx2 630 | --[[[end]]] 631 | """ 632 | 633 | infile = reindentBlock(infile) 634 | self.assertEqual(Cog().processString(infile), infile) 635 | 636 | 637 | class CogGeneratorGetCodeTests(TestCase): 638 | """ Unit tests against CogGenerator to see if its getCode() method works 639 | properly. 640 | """ 641 | 642 | def setUp(self): 643 | """ All tests get a generator to use, and short same-length names for 644 | the functions we're going to use. 645 | """ 646 | self.gen = CogGenerator() 647 | self.m = self.gen.parseMarker 648 | self.l = self.gen.parseLine 649 | 650 | def testEmpty(self): 651 | self.m('// [[[cog') 652 | self.m('// ]]]') 653 | self.assertEqual(self.gen.getCode(), '') 654 | 655 | def testSimple(self): 656 | self.m('// [[[cog') 657 | self.l(' print "hello"') 658 | self.l(' print "bye"') 659 | self.m('// ]]]') 660 | self.assertEqual(self.gen.getCode(), 'print "hello"\nprint "bye"') 661 | 662 | def testCompressed1(self): 663 | # For a while, I supported compressed code blocks, but no longer. 664 | self.m('// [[[cog: print """') 665 | self.l('// hello') 666 | self.l('// bye') 667 | self.m('// """)]]]') 668 | self.assertEqual(self.gen.getCode(), 'hello\nbye') 669 | 670 | def testCompressed2(self): 671 | # For a while, I supported compressed code blocks, but no longer. 672 | self.m('// [[[cog: print """') 673 | self.l('hello') 674 | self.l('bye') 675 | self.m('// """)]]]') 676 | self.assertEqual(self.gen.getCode(), 'hello\nbye') 677 | 678 | def testCompressed3(self): 679 | # For a while, I supported compressed code blocks, but no longer. 680 | self.m('// [[[cog') 681 | self.l('print """hello') 682 | self.l('bye') 683 | self.m('// """)]]]') 684 | self.assertEqual(self.gen.getCode(), 'print """hello\nbye') 685 | 686 | def testCompressed4(self): 687 | # For a while, I supported compressed code blocks, but no longer. 688 | self.m('// [[[cog: print """') 689 | self.l('hello') 690 | self.l('bye""")') 691 | self.m('// ]]]') 692 | self.assertEqual(self.gen.getCode(), 'hello\nbye""")') 693 | 694 | def testNoCommonPrefixForMarkers(self): 695 | # It's important to be able to use #if 0 to hide lines from a 696 | # C++ compiler. 697 | self.m('#if 0 //[[[cog') 698 | self.l('\timport cog, sys') 699 | self.l('') 700 | self.l('\tprint sys.argv') 701 | self.m('#endif //]]]') 702 | self.assertEqual(self.gen.getCode(), 'import cog, sys\n\nprint sys.argv') 703 | 704 | 705 | class TestCaseWithTempDir(TestCase): 706 | 707 | def newCog(self): 708 | """ Initialize the cog members for another run. 709 | """ 710 | # Create a cog engine, and catch its output. 711 | self.cog = Cog() 712 | self.output = StringIO() 713 | self.cog.setOutput(stdout=self.output, stderr=self.output) 714 | 715 | def setUp(self): 716 | # Create a temporary directory. 717 | self.tempdir = os.path.join(tempfile.gettempdir(), 'testcog_tempdir_' + str(random.random())[2:]) 718 | os.mkdir(self.tempdir) 719 | self.olddir = os.getcwd() 720 | os.chdir(self.tempdir) 721 | self.newCog() 722 | 723 | def tearDown(self): 724 | os.chdir(self.olddir) 725 | # Get rid of the temporary directory. 726 | shutil.rmtree(self.tempdir) 727 | 728 | def assertFilesSame(self, sFName1, sFName2): 729 | text1 = open(os.path.join(self.tempdir, sFName1), 'rb').read() 730 | text2 = open(os.path.join(self.tempdir, sFName2), 'rb').read() 731 | self.assertEqual(text1, text2) 732 | 733 | def assertFileContent(self, sFName, sContent): 734 | sAbsName = os.path.join(self.tempdir, sFName) 735 | f = open(sAbsName, 'rb') 736 | try: 737 | sFileContent = f.read() 738 | finally: 739 | f.close() 740 | self.assertEqual(sFileContent, to_bytes(sContent)) 741 | 742 | 743 | class ArgumentHandlingTests(TestCaseWithTempDir): 744 | 745 | def testArgumentFailure(self): 746 | # Return value 2 means usage problem. 747 | self.assertEqual(self.cog.main(['argv0', '-j']), 2) 748 | output = self.output.getvalue() 749 | self.assertIn("option -j not recognized", output) 750 | with self.assertRaises(CogUsageError): 751 | self.cog.callableMain(['argv0']) 752 | with self.assertRaises(CogUsageError): 753 | self.cog.callableMain(['argv0', '-j']) 754 | 755 | def testNoDashOAndAtFile(self): 756 | d = { 757 | 'cogfiles.txt': """\ 758 | # Please run cog 759 | """ 760 | } 761 | 762 | makeFiles(d) 763 | with self.assertRaises(CogUsageError): 764 | self.cog.callableMain(['argv0', '-o', 'foo', '@cogfiles.txt']) 765 | 766 | def testDashV(self): 767 | self.assertEqual(self.cog.main(['argv0', '-v']), 0) 768 | output = self.output.getvalue() 769 | self.assertEqual('Cog version %s\n' % __version__, output) 770 | 771 | def producesHelp(self, args): 772 | self.newCog() 773 | argv = ['argv0'] + args.split() 774 | self.assertEqual(self.cog.main(argv), 0) 775 | self.assertEqual(usage, self.output.getvalue()) 776 | 777 | def testDashH(self): 778 | # -h or -? anywhere on the command line should just print help. 779 | self.producesHelp("-h") 780 | self.producesHelp("-?") 781 | self.producesHelp("fooey.txt -h") 782 | self.producesHelp("-o -r @fooey.txt -? @booey.txt") 783 | 784 | def testDashOAndDashR(self): 785 | d = { 786 | 'cogfile.txt': """\ 787 | # Please run cog 788 | """ 789 | } 790 | 791 | makeFiles(d) 792 | with self.assertRaises(CogUsageError): 793 | self.cog.callableMain(['argv0', '-o', 'foo', '-r', 'cogfile.txt']) 794 | 795 | def testDashZ(self): 796 | d = { 797 | 'test.cog': """\ 798 | // This is my C++ file. 799 | //[[[cog 800 | fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 801 | for fn in fnames: 802 | cog.outl("void %s();" % fn) 803 | //]]] 804 | """, 805 | 806 | 'test.out': """\ 807 | // This is my C++ file. 808 | //[[[cog 809 | fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 810 | for fn in fnames: 811 | cog.outl("void %s();" % fn) 812 | //]]] 813 | void DoSomething(); 814 | void DoAnotherThing(); 815 | void DoLastThing(); 816 | """, 817 | } 818 | 819 | makeFiles(d) 820 | with self.assertRaisesRegexp(CogError, re.escape("test.cog(6): Missing '[[[end]]]' before end of file.")): 821 | self.cog.callableMain(['argv0', '-r', 'test.cog']) 822 | self.newCog() 823 | self.cog.callableMain(['argv0', '-r', '-z', 'test.cog']) 824 | self.assertFilesSame('test.cog', 'test.out') 825 | 826 | def testBadDashD(self): 827 | with self.assertRaises(CogUsageError): 828 | self.cog.callableMain(['argv0', '-Dfooey', 'cog.txt']) 829 | with self.assertRaises(CogUsageError): 830 | self.cog.callableMain(['argv0', '-D', 'fooey', 'cog.txt']) 831 | 832 | 833 | class TestFileHandling(TestCaseWithTempDir): 834 | 835 | def testSimple(self): 836 | d = { 837 | 'test.cog': """\ 838 | // This is my C++ file. 839 | //[[[cog 840 | fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 841 | for fn in fnames: 842 | cog.outl("void %s();" % fn) 843 | //]]] 844 | //[[[end]]] 845 | """, 846 | 847 | 'test.out': """\ 848 | // This is my C++ file. 849 | //[[[cog 850 | fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 851 | for fn in fnames: 852 | cog.outl("void %s();" % fn) 853 | //]]] 854 | void DoSomething(); 855 | void DoAnotherThing(); 856 | void DoLastThing(); 857 | //[[[end]]] 858 | """, 859 | } 860 | 861 | makeFiles(d) 862 | self.cog.callableMain(['argv0', '-r', 'test.cog']) 863 | self.assertFilesSame('test.cog', 'test.out') 864 | output = self.output.getvalue() 865 | self.assertIn("(changed)", output) 866 | 867 | def testOutputFile(self): 868 | # -o sets the output file. 869 | d = { 870 | 'test.cog': """\ 871 | // This is my C++ file. 872 | //[[[cog 873 | fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 874 | for fn in fnames: 875 | cog.outl("void %s();" % fn) 876 | //]]] 877 | //[[[end]]] 878 | """, 879 | 880 | 'test.out': """\ 881 | // This is my C++ file. 882 | //[[[cog 883 | fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 884 | for fn in fnames: 885 | cog.outl("void %s();" % fn) 886 | //]]] 887 | void DoSomething(); 888 | void DoAnotherThing(); 889 | void DoLastThing(); 890 | //[[[end]]] 891 | """, 892 | } 893 | 894 | makeFiles(d) 895 | self.cog.callableMain(['argv0', '-o', 'test.cogged', 'test.cog']) 896 | self.assertFilesSame('test.cogged', 'test.out') 897 | 898 | def testAtFile(self): 899 | d = { 900 | 'one.cog': """\ 901 | //[[[cog 902 | cog.outl("hello world") 903 | //]]] 904 | //[[[end]]] 905 | """, 906 | 907 | 'one.out': """\ 908 | //[[[cog 909 | cog.outl("hello world") 910 | //]]] 911 | hello world 912 | //[[[end]]] 913 | """, 914 | 915 | 'two.cog': """\ 916 | //[[[cog 917 | cog.outl("goodbye cruel world") 918 | //]]] 919 | //[[[end]]] 920 | """, 921 | 922 | 'two.out': """\ 923 | //[[[cog 924 | cog.outl("goodbye cruel world") 925 | //]]] 926 | goodbye cruel world 927 | //[[[end]]] 928 | """, 929 | 930 | 'cogfiles.txt': """\ 931 | # Please run cog 932 | one.cog 933 | 934 | two.cog 935 | """ 936 | } 937 | 938 | makeFiles(d) 939 | self.cog.callableMain(['argv0', '-r', '@cogfiles.txt']) 940 | self.assertFilesSame('one.cog', 'one.out') 941 | self.assertFilesSame('two.cog', 'two.out') 942 | output = self.output.getvalue() 943 | self.assertIn("(changed)", output) 944 | 945 | def testNestedAtFile(self): 946 | d = { 947 | 'one.cog': """\ 948 | //[[[cog 949 | cog.outl("hello world") 950 | //]]] 951 | //[[[end]]] 952 | """, 953 | 954 | 'one.out': """\ 955 | //[[[cog 956 | cog.outl("hello world") 957 | //]]] 958 | hello world 959 | //[[[end]]] 960 | """, 961 | 962 | 'two.cog': """\ 963 | //[[[cog 964 | cog.outl("goodbye cruel world") 965 | //]]] 966 | //[[[end]]] 967 | """, 968 | 969 | 'two.out': """\ 970 | //[[[cog 971 | cog.outl("goodbye cruel world") 972 | //]]] 973 | goodbye cruel world 974 | //[[[end]]] 975 | """, 976 | 977 | 'cogfiles.txt': """\ 978 | # Please run cog 979 | one.cog 980 | @cogfiles2.txt 981 | """, 982 | 983 | 'cogfiles2.txt': """\ 984 | # This one too, please. 985 | two.cog 986 | """, 987 | } 988 | 989 | makeFiles(d) 990 | self.cog.callableMain(['argv0', '-r', '@cogfiles.txt']) 991 | self.assertFilesSame('one.cog', 'one.out') 992 | self.assertFilesSame('two.cog', 'two.out') 993 | output = self.output.getvalue() 994 | self.assertIn("(changed)", output) 995 | 996 | def testAtFileWithArgs(self): 997 | d = { 998 | 'both.cog': """\ 999 | //[[[cog 1000 | cog.outl("one: %s" % ('one' in globals())) 1001 | cog.outl("two: %s" % ('two' in globals())) 1002 | //]]] 1003 | //[[[end]]] 1004 | """, 1005 | 1006 | 'one.out': """\ 1007 | //[[[cog 1008 | cog.outl("one: %s" % ('one' in globals())) 1009 | cog.outl("two: %s" % ('two' in globals())) 1010 | //]]] 1011 | one: True // ONE 1012 | two: False // ONE 1013 | //[[[end]]] 1014 | """, 1015 | 1016 | 'two.out': """\ 1017 | //[[[cog 1018 | cog.outl("one: %s" % ('one' in globals())) 1019 | cog.outl("two: %s" % ('two' in globals())) 1020 | //]]] 1021 | one: False // TWO 1022 | two: True // TWO 1023 | //[[[end]]] 1024 | """, 1025 | 1026 | 'cogfiles.txt': """\ 1027 | # Please run cog 1028 | both.cog -o both.one -s ' // ONE' -D one=x 1029 | both.cog -o both.two -s ' // TWO' -D two=x 1030 | """ 1031 | } 1032 | 1033 | makeFiles(d) 1034 | self.cog.callableMain(['argv0', '@cogfiles.txt']) 1035 | self.assertFilesSame('both.one', 'one.out') 1036 | self.assertFilesSame('both.two', 'two.out') 1037 | 1038 | def testAtFileWithBadArgCombo(self): 1039 | d = { 1040 | 'both.cog': """\ 1041 | //[[[cog 1042 | cog.outl("one: %s" % ('one' in globals())) 1043 | cog.outl("two: %s" % ('two' in globals())) 1044 | //]]] 1045 | //[[[end]]] 1046 | """, 1047 | 1048 | 'cogfiles.txt': """\ 1049 | # Please run cog 1050 | both.cog 1051 | both.cog -d # This is bad: -r and -d 1052 | """ 1053 | } 1054 | 1055 | makeFiles(d) 1056 | with self.assertRaises(CogUsageError): 1057 | self.cog.callableMain(['argv0', '-r', '@cogfiles.txt']) 1058 | 1059 | def testAtFileWithTrickyFilenames(self): 1060 | def fix_backslashes(files_txt): 1061 | """Make the contents of a files.txt sensitive to the platform.""" 1062 | if sys.platform != "win32": 1063 | files_txt = files_txt.replace("\\", "/") 1064 | return files_txt 1065 | 1066 | d = { 1067 | 'one 1.cog': """\ 1068 | //[[[cog cog.outl("hello world") ]]] 1069 | """, 1070 | 1071 | 'one.out': """\ 1072 | //[[[cog cog.outl("hello world") ]]] 1073 | hello world //xxx 1074 | """, 1075 | 1076 | 'subdir': { 1077 | 'subback.cog': """\ 1078 | //[[[cog cog.outl("down deep with backslashes") ]]] 1079 | """, 1080 | 1081 | 'subfwd.cog': """\ 1082 | //[[[cog cog.outl("down deep with slashes") ]]] 1083 | """, 1084 | }, 1085 | 1086 | 'subback.out': """\ 1087 | //[[[cog cog.outl("down deep with backslashes") ]]] 1088 | down deep with backslashes //yyy 1089 | """, 1090 | 1091 | 'subfwd.out': """\ 1092 | //[[[cog cog.outl("down deep with slashes") ]]] 1093 | down deep with slashes //zzz 1094 | """, 1095 | 1096 | 'cogfiles.txt': fix_backslashes("""\ 1097 | # Please run cog 1098 | 'one 1.cog' -s ' //xxx' 1099 | subdir\\subback.cog -s ' //yyy' 1100 | subdir/subfwd.cog -s ' //zzz' 1101 | """) 1102 | } 1103 | 1104 | makeFiles(d) 1105 | self.cog.callableMain(['argv0', '-z', '-r', '@cogfiles.txt']) 1106 | self.assertFilesSame('one 1.cog', 'one.out') 1107 | self.assertFilesSame('subdir/subback.cog', 'subback.out') 1108 | self.assertFilesSame('subdir/subfwd.cog', 'subfwd.out') 1109 | 1110 | 1111 | class CogTestLineEndings(TestCaseWithTempDir): 1112 | """Tests for -U option (force LF line-endings in output).""" 1113 | 1114 | lines_in = ['Some text.', 1115 | '//[[[cog', 1116 | 'cog.outl("Cog text")', 1117 | '//]]]', 1118 | 'gobbledegook.', 1119 | '//[[[end]]]', 1120 | 'epilogue.', 1121 | ''] 1122 | 1123 | lines_out = ['Some text.', 1124 | '//[[[cog', 1125 | 'cog.outl("Cog text")', 1126 | '//]]]', 1127 | 'Cog text', 1128 | '//[[[end]]]', 1129 | 'epilogue.', 1130 | ''] 1131 | 1132 | def testOutputNativeEol(self): 1133 | makeFiles({'infile': '\n'.join(self.lines_in)}) 1134 | self.cog.callableMain(['argv0', '-o', 'outfile', 'infile']) 1135 | self.assertFileContent('outfile', os.linesep.join(self.lines_out)) 1136 | 1137 | def testOutputLfEol(self): 1138 | makeFiles({'infile': '\n'.join(self.lines_in)}) 1139 | self.cog.callableMain(['argv0', '-U', '-o', 'outfile', 'infile']) 1140 | self.assertFileContent('outfile', '\n'.join(self.lines_out)) 1141 | 1142 | def testReplaceNativeEol(self): 1143 | makeFiles({'test.cog': '\n'.join(self.lines_in)}) 1144 | self.cog.callableMain(['argv0', '-r', 'test.cog']) 1145 | self.assertFileContent('test.cog', os.linesep.join(self.lines_out)) 1146 | 1147 | def testReplaceLfEol(self): 1148 | makeFiles({'test.cog': '\n'.join(self.lines_in)}) 1149 | self.cog.callableMain(['argv0', '-U', '-r', 'test.cog']) 1150 | self.assertFileContent('test.cog', '\n'.join(self.lines_out)) 1151 | 1152 | 1153 | class CogTestCharacterEncoding(TestCaseWithTempDir): 1154 | 1155 | def testSimple(self): 1156 | d = { 1157 | 'test.cog': b("""\ 1158 | // This is my C++ file. 1159 | //[[[cog 1160 | cog.outl("// Unicode: \xe1\x88\xb4 (U+1234)") 1161 | //]]] 1162 | //[[[end]]] 1163 | """), 1164 | 1165 | 'test.out': b("""\ 1166 | // This is my C++ file. 1167 | //[[[cog 1168 | cog.outl("// Unicode: \xe1\x88\xb4 (U+1234)") 1169 | //]]] 1170 | // Unicode: \xe1\x88\xb4 (U+1234) 1171 | //[[[end]]] 1172 | """), 1173 | } 1174 | 1175 | makeFiles(d) 1176 | self.cog.callableMain(['argv0', '-r', 'test.cog']) 1177 | self.assertFilesSame('test.cog', 'test.out') 1178 | output = self.output.getvalue() 1179 | self.assertIn("(changed)", output) 1180 | 1181 | def testFileEncodingOption(self): 1182 | d = { 1183 | 'test.cog': b("""\ 1184 | // \xca\xee\xe4\xe8\xf0\xe2\xea\xe0 Windows 1185 | //[[[cog 1186 | cog.outl("\xd1\xfa\xe5\xf8\xfc \xe5\xf9\xb8 \xfd\xf2\xe8\xf5 \xec\xff\xe3\xea\xe8\xf5 \xf4\xf0\xe0\xed\xf6\xf3\xe7\xf1\xea\xe8\xf5 \xe1\xf3\xeb\xee\xea \xe4\xe0 \xe2\xfb\xef\xe5\xe9 \xf7\xe0\xfe") 1187 | //]]] 1188 | //[[[end]]] 1189 | """), 1190 | 1191 | 'test.out': b("""\ 1192 | // \xca\xee\xe4\xe8\xf0\xe2\xea\xe0 Windows 1193 | //[[[cog 1194 | cog.outl("\xd1\xfa\xe5\xf8\xfc \xe5\xf9\xb8 \xfd\xf2\xe8\xf5 \xec\xff\xe3\xea\xe8\xf5 \xf4\xf0\xe0\xed\xf6\xf3\xe7\xf1\xea\xe8\xf5 \xe1\xf3\xeb\xee\xea \xe4\xe0 \xe2\xfb\xef\xe5\xe9 \xf7\xe0\xfe") 1195 | //]]] 1196 | \xd1\xfa\xe5\xf8\xfc \xe5\xf9\xb8 \xfd\xf2\xe8\xf5 \xec\xff\xe3\xea\xe8\xf5 \xf4\xf0\xe0\xed\xf6\xf3\xe7\xf1\xea\xe8\xf5 \xe1\xf3\xeb\xee\xea \xe4\xe0 \xe2\xfb\xef\xe5\xe9 \xf7\xe0\xfe 1197 | //[[[end]]] 1198 | """), 1199 | } 1200 | 1201 | makeFiles(d) 1202 | self.cog.callableMain(['argv0', '-n', 'cp1251', '-r', 'test.cog']) 1203 | self.assertFilesSame('test.cog', 'test.out') 1204 | output = self.output.getvalue() 1205 | self.assertIn("(changed)", output) 1206 | 1207 | 1208 | class TestCaseWithImports(TestCaseWithTempDir): 1209 | """ When running tests which import modules, the sys.modules list 1210 | leaks from one test to the next. This test case class scrubs 1211 | the list after each run to keep the tests isolated from each other. 1212 | """ 1213 | 1214 | def setUp(self): 1215 | TestCaseWithTempDir.setUp(self) 1216 | self.sysmodulekeys = list(sys.modules) 1217 | 1218 | def tearDown(self): 1219 | modstoscrub = [ 1220 | modname 1221 | for modname in sys.modules 1222 | if modname not in self.sysmodulekeys 1223 | ] 1224 | for modname in modstoscrub: 1225 | del sys.modules[modname] 1226 | TestCaseWithTempDir.tearDown(self) 1227 | 1228 | 1229 | class CogIncludeTests(TestCaseWithImports): 1230 | dincludes = { 1231 | 'test.cog': """\ 1232 | //[[[cog 1233 | import mymodule 1234 | //]]] 1235 | //[[[end]]] 1236 | """, 1237 | 1238 | 'test.out': """\ 1239 | //[[[cog 1240 | import mymodule 1241 | //]]] 1242 | Hello from mymodule 1243 | //[[[end]]] 1244 | """, 1245 | 1246 | 'test2.out': """\ 1247 | //[[[cog 1248 | import mymodule 1249 | //]]] 1250 | Hello from mymodule in inc2 1251 | //[[[end]]] 1252 | """, 1253 | 1254 | 'include': { 1255 | 'mymodule.py': """\ 1256 | import cog 1257 | cog.outl("Hello from mymodule") 1258 | """ 1259 | }, 1260 | 1261 | 'inc2': { 1262 | 'mymodule.py': """\ 1263 | import cog 1264 | cog.outl("Hello from mymodule in inc2") 1265 | """ 1266 | }, 1267 | 1268 | 'inc3': { 1269 | 'someothermodule.py': """\ 1270 | import cog 1271 | cog.outl("This is some other module.") 1272 | """ 1273 | }, 1274 | } 1275 | 1276 | def testNeedIncludePath(self): 1277 | # Try it without the -I, to see that an ImportError happens. 1278 | makeFiles(self.dincludes) 1279 | with self.assertRaises(ImportError): 1280 | self.cog.callableMain(['argv0', '-r', 'test.cog']) 1281 | 1282 | def testIncludePath(self): 1283 | # Test that -I adds include directories properly. 1284 | makeFiles(self.dincludes) 1285 | self.cog.callableMain(['argv0', '-r', '-I', 'include', 'test.cog']) 1286 | self.assertFilesSame('test.cog', 'test.out') 1287 | 1288 | def testTwoIncludePaths(self): 1289 | # Test that two -I's add include directories properly. 1290 | makeFiles(self.dincludes) 1291 | self.cog.callableMain(['argv0', '-r', '-I', 'include', '-I', 'inc2', 'test.cog']) 1292 | self.assertFilesSame('test.cog', 'test.out') 1293 | 1294 | def testTwoIncludePaths2(self): 1295 | # Test that two -I's add include directories properly. 1296 | makeFiles(self.dincludes) 1297 | self.cog.callableMain(['argv0', '-r', '-I', 'inc2', '-I', 'include', 'test.cog']) 1298 | self.assertFilesSame('test.cog', 'test2.out') 1299 | 1300 | def testUselessIncludePath(self): 1301 | # Test that the search will continue past the first directory. 1302 | makeFiles(self.dincludes) 1303 | self.cog.callableMain(['argv0', '-r', '-I', 'inc3', '-I', 'include', 'test.cog']) 1304 | self.assertFilesSame('test.cog', 'test.out') 1305 | 1306 | def testSysPathIsUnchanged(self): 1307 | d = { 1308 | 'bad.cog': """\ 1309 | //[[[cog cog.error("Oh no!") ]]] 1310 | //[[[end]]] 1311 | """, 1312 | 'good.cog': """\ 1313 | //[[[cog cog.outl("Oh yes!") ]]] 1314 | //[[[end]]] 1315 | """, 1316 | } 1317 | 1318 | makeFiles(d) 1319 | # Is it unchanged just by creating a cog engine? 1320 | oldsyspath = sys.path[:] 1321 | self.newCog() 1322 | self.assertEqual(oldsyspath, sys.path) 1323 | # Is it unchanged for a successful run? 1324 | self.newCog() 1325 | self.cog.callableMain(['argv0', '-r', 'good.cog']) 1326 | self.assertEqual(oldsyspath, sys.path) 1327 | # Is it unchanged for a successful run with includes? 1328 | self.newCog() 1329 | self.cog.callableMain(['argv0', '-r', '-I', 'xyzzy', 'good.cog']) 1330 | self.assertEqual(oldsyspath, sys.path) 1331 | # Is it unchanged for a successful run with two includes? 1332 | self.newCog() 1333 | self.cog.callableMain(['argv0', '-r', '-I', 'xyzzy', '-I', 'quux', 'good.cog']) 1334 | self.assertEqual(oldsyspath, sys.path) 1335 | # Is it unchanged for a failed run? 1336 | self.newCog() 1337 | with self.assertRaises(CogError): 1338 | self.cog.callableMain(['argv0', '-r', 'bad.cog']) 1339 | self.assertEqual(oldsyspath, sys.path) 1340 | # Is it unchanged for a failed run with includes? 1341 | self.newCog() 1342 | with self.assertRaises(CogError): 1343 | self.cog.callableMain(['argv0', '-r', '-I', 'xyzzy', 'bad.cog']) 1344 | self.assertEqual(oldsyspath, sys.path) 1345 | # Is it unchanged for a failed run with two includes? 1346 | self.newCog() 1347 | with self.assertRaises(CogError): 1348 | self.cog.callableMain(['argv0', '-r', '-I', 'xyzzy', '-I', 'quux', 'bad.cog']) 1349 | self.assertEqual(oldsyspath, sys.path) 1350 | 1351 | def testSubDirectories(self): 1352 | # Test that relative paths on the command line work, with includes. 1353 | 1354 | d = { 1355 | 'code': { 1356 | 'test.cog': """\ 1357 | //[[[cog 1358 | import mysubmodule 1359 | //]]] 1360 | //[[[end]]] 1361 | """, 1362 | 1363 | 'test.out': """\ 1364 | //[[[cog 1365 | import mysubmodule 1366 | //]]] 1367 | Hello from mysubmodule 1368 | //[[[end]]] 1369 | """, 1370 | 1371 | 'mysubmodule.py': """\ 1372 | import cog 1373 | cog.outl("Hello from mysubmodule") 1374 | """ 1375 | } 1376 | } 1377 | 1378 | makeFiles(d) 1379 | # We should be able to invoke cog without the -I switch, and it will 1380 | # auto-include the current directory 1381 | self.cog.callableMain(['argv0', '-r', 'code/test.cog']) 1382 | self.assertFilesSame('code/test.cog', 'code/test.out') 1383 | 1384 | 1385 | class CogTestsInFiles(TestCaseWithTempDir): 1386 | 1387 | def testWarnIfNoCogCode(self): 1388 | # Test that the -e switch warns if there is no Cog code. 1389 | d = { 1390 | 'with.cog': """\ 1391 | //[[[cog 1392 | cog.outl("hello world") 1393 | //]]] 1394 | hello world 1395 | //[[[end]]] 1396 | """, 1397 | 1398 | 'without.cog': """\ 1399 | There's no cog 1400 | code in this file. 1401 | """, 1402 | } 1403 | 1404 | makeFiles(d) 1405 | self.cog.callableMain(['argv0', '-e', 'with.cog']) 1406 | output = self.output.getvalue() 1407 | self.assertNotIn("Warning", output) 1408 | self.newCog() 1409 | self.cog.callableMain(['argv0', '-e', 'without.cog']) 1410 | output = self.output.getvalue() 1411 | self.assertIn("Warning: no cog code found in without.cog", output) 1412 | self.newCog() 1413 | self.cog.callableMain(['argv0', 'without.cog']) 1414 | output = self.output.getvalue() 1415 | self.assertNotIn("Warning", output) 1416 | 1417 | def testFileNameProps(self): 1418 | d = { 1419 | 'cog1.txt': """\ 1420 | //[[[cog 1421 | cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile)) 1422 | //]]] 1423 | this is cog1.txt in, cog1.txt out 1424 | [[[end]]] 1425 | """, 1426 | 1427 | 'cog1.out': """\ 1428 | //[[[cog 1429 | cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile)) 1430 | //]]] 1431 | This is cog1.txt in, cog1.txt out 1432 | [[[end]]] 1433 | """, 1434 | 1435 | 'cog1out.out': """\ 1436 | //[[[cog 1437 | cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile)) 1438 | //]]] 1439 | This is cog1.txt in, cog1out.txt out 1440 | [[[end]]] 1441 | """, 1442 | } 1443 | 1444 | makeFiles(d) 1445 | self.cog.callableMain(['argv0', '-r', 'cog1.txt']) 1446 | self.assertFilesSame('cog1.txt', 'cog1.out') 1447 | self.newCog() 1448 | self.cog.callableMain(['argv0', '-o', 'cog1out.txt', 'cog1.txt']) 1449 | self.assertFilesSame('cog1out.txt', 'cog1out.out') 1450 | 1451 | def testGlobalsDontCrossFiles(self): 1452 | # Make sure that global values don't get shared between files. 1453 | d = { 1454 | 'one.cog': """\ 1455 | //[[[cog s = "This was set in one.cog" ]]] 1456 | //[[[end]]] 1457 | //[[[cog cog.outl(s) ]]] 1458 | //[[[end]]] 1459 | """, 1460 | 1461 | 'one.out': """\ 1462 | //[[[cog s = "This was set in one.cog" ]]] 1463 | //[[[end]]] 1464 | //[[[cog cog.outl(s) ]]] 1465 | This was set in one.cog 1466 | //[[[end]]] 1467 | """, 1468 | 1469 | 'two.cog': """\ 1470 | //[[[cog 1471 | try: 1472 | cog.outl(s) 1473 | except NameError: 1474 | cog.outl("s isn't set!") 1475 | //]]] 1476 | //[[[end]]] 1477 | """, 1478 | 1479 | 'two.out': """\ 1480 | //[[[cog 1481 | try: 1482 | cog.outl(s) 1483 | except NameError: 1484 | cog.outl("s isn't set!") 1485 | //]]] 1486 | s isn't set! 1487 | //[[[end]]] 1488 | """, 1489 | 1490 | 'cogfiles.txt': """\ 1491 | # Please run cog 1492 | one.cog 1493 | 1494 | two.cog 1495 | """ 1496 | } 1497 | 1498 | makeFiles(d) 1499 | self.cog.callableMain(['argv0', '-r', '@cogfiles.txt']) 1500 | self.assertFilesSame('one.cog', 'one.out') 1501 | self.assertFilesSame('two.cog', 'two.out') 1502 | output = self.output.getvalue() 1503 | self.assertIn("(changed)", output) 1504 | 1505 | def testRemoveGeneratedOutput(self): 1506 | d = { 1507 | 'cog1.txt': """\ 1508 | //[[[cog 1509 | cog.outl("This line was generated.") 1510 | //]]] 1511 | This line was generated. 1512 | //[[[end]]] 1513 | This line was not. 1514 | """, 1515 | 1516 | 'cog1.out': """\ 1517 | //[[[cog 1518 | cog.outl("This line was generated.") 1519 | //]]] 1520 | //[[[end]]] 1521 | This line was not. 1522 | """, 1523 | 1524 | 'cog1.out2': """\ 1525 | //[[[cog 1526 | cog.outl("This line was generated.") 1527 | //]]] 1528 | This line was generated. 1529 | //[[[end]]] 1530 | This line was not. 1531 | """, 1532 | } 1533 | 1534 | makeFiles(d) 1535 | # Remove generated output. 1536 | self.cog.callableMain(['argv0', '-r', '-x', 'cog1.txt']) 1537 | self.assertFilesSame('cog1.txt', 'cog1.out') 1538 | self.newCog() 1539 | # Regenerate the generated output. 1540 | self.cog.callableMain(['argv0', '-r', 'cog1.txt']) 1541 | self.assertFilesSame('cog1.txt', 'cog1.out2') 1542 | self.newCog() 1543 | # Remove the generated output again. 1544 | self.cog.callableMain(['argv0', '-r', '-x', 'cog1.txt']) 1545 | self.assertFilesSame('cog1.txt', 'cog1.out') 1546 | 1547 | def testMsgCall(self): 1548 | infile = """\ 1549 | #[[[cog 1550 | cog.msg("Hello there!") 1551 | #]]] 1552 | #[[[end]]] 1553 | """ 1554 | infile = reindentBlock(infile) 1555 | self.assertEqual(self.cog.processString(infile), infile) 1556 | output = self.output.getvalue() 1557 | self.assertEqual(output, "Message: Hello there!\n") 1558 | 1559 | def testErrorMessageHasNoTraceback(self): 1560 | # Test that a Cog error is printed to stderr with no traceback. 1561 | 1562 | d = { 1563 | 'cog1.txt': """\ 1564 | //[[[cog 1565 | cog.outl("This line was newly") 1566 | cog.outl("generated by cog") 1567 | cog.outl("blah blah.") 1568 | //]]] 1569 | Xhis line was newly 1570 | generated by cog 1571 | blah blah. 1572 | //[[[end]]] (checksum: a8540982e5ad6b95c9e9a184b26f4346) 1573 | """, 1574 | } 1575 | 1576 | makeFiles(d) 1577 | stderr = StringIO() 1578 | self.cog.setOutput(stderr=stderr) 1579 | self.cog.main(['argv0', '-c', '-r', "cog1.txt"]) 1580 | output = self.output.getvalue() 1581 | self.assertEqual(self.output.getvalue(), "Cogging cog1.txt\n") 1582 | self.assertEqual(stderr.getvalue(), "cog1.txt(9): Output has been edited! Delete old checksum to unprotect.\n") 1583 | 1584 | def testDashD(self): 1585 | d = { 1586 | 'test.cog': """\ 1587 | --[[[cog cog.outl("Defined fooey as " + fooey) ]]] 1588 | --[[[end]]] 1589 | """, 1590 | 1591 | 'test.kablooey': """\ 1592 | --[[[cog cog.outl("Defined fooey as " + fooey) ]]] 1593 | Defined fooey as kablooey 1594 | --[[[end]]] 1595 | """, 1596 | 1597 | 'test.einstein': """\ 1598 | --[[[cog cog.outl("Defined fooey as " + fooey) ]]] 1599 | Defined fooey as e=mc2 1600 | --[[[end]]] 1601 | """, 1602 | } 1603 | 1604 | makeFiles(d) 1605 | self.cog.callableMain(['argv0', '-r', '-D', 'fooey=kablooey', 'test.cog']) 1606 | self.assertFilesSame('test.cog', 'test.kablooey') 1607 | makeFiles(d) 1608 | self.cog.callableMain(['argv0', '-r', '-Dfooey=kablooey', 'test.cog']) 1609 | self.assertFilesSame('test.cog', 'test.kablooey') 1610 | makeFiles(d) 1611 | self.cog.callableMain(['argv0', '-r', '-Dfooey=e=mc2', 'test.cog']) 1612 | self.assertFilesSame('test.cog', 'test.einstein') 1613 | makeFiles(d) 1614 | self.cog.callableMain(['argv0', '-r', '-Dbar=quux', '-Dfooey=kablooey', 'test.cog']) 1615 | self.assertFilesSame('test.cog', 'test.kablooey') 1616 | makeFiles(d) 1617 | self.cog.callableMain(['argv0', '-r', '-Dfooey=kablooey', '-Dbar=quux', 'test.cog']) 1618 | self.assertFilesSame('test.cog', 'test.kablooey') 1619 | makeFiles(d) 1620 | self.cog.callableMain(['argv0', '-r', '-Dfooey=gooey', '-Dfooey=kablooey', 'test.cog']) 1621 | self.assertFilesSame('test.cog', 'test.kablooey') 1622 | 1623 | def testOutputToStdout(self): 1624 | d = { 1625 | 'test.cog': """\ 1626 | --[[[cog cog.outl('Hey there!') ]]] 1627 | --[[[end]]] 1628 | """ 1629 | } 1630 | 1631 | makeFiles(d) 1632 | stderr = StringIO() 1633 | self.cog.setOutput(stderr=stderr) 1634 | self.cog.callableMain(['argv0', 'test.cog']) 1635 | output = self.output.getvalue() 1636 | outerr = stderr.getvalue() 1637 | self.assertEqual(output, "--[[[cog cog.outl('Hey there!') ]]]\nHey there!\n--[[[end]]]\n") 1638 | self.assertEqual(outerr, "") 1639 | 1640 | def testReadFromStdin(self): 1641 | stdin = StringIO("--[[[cog cog.outl('Wow') ]]]\n--[[[end]]]\n") 1642 | def restore_stdin(old_stdin): 1643 | sys.stdin = old_stdin 1644 | self.addCleanup(restore_stdin, sys.stdin) 1645 | sys.stdin = stdin 1646 | 1647 | stderr = StringIO() 1648 | self.cog.setOutput(stderr=stderr) 1649 | self.cog.callableMain(['argv0', '-']) 1650 | output = self.output.getvalue() 1651 | outerr = stderr.getvalue() 1652 | self.assertEqual(output, "--[[[cog cog.outl('Wow') ]]]\nWow\n--[[[end]]]\n") 1653 | self.assertEqual(outerr, "") 1654 | 1655 | 1656 | def testSuffixOutputLines(self): 1657 | d = { 1658 | 'test.cog': """\ 1659 | Hey there. 1660 | ;[[[cog cog.outl('a\\nb\\n \\nc') ]]] 1661 | ;[[[end]]] 1662 | Good bye. 1663 | """, 1664 | 1665 | 'test.out': """\ 1666 | Hey there. 1667 | ;[[[cog cog.outl('a\\nb\\n \\nc') ]]] 1668 | a (foo) 1669 | b (foo) 1670 | """ # These three trailing spaces are important. 1671 | # The suffix is not applied to completely blank lines. 1672 | """ 1673 | c (foo) 1674 | ;[[[end]]] 1675 | Good bye. 1676 | """, 1677 | } 1678 | 1679 | makeFiles(d) 1680 | self.cog.callableMain(['argv0', '-r', '-s', ' (foo)', 'test.cog']) 1681 | self.assertFilesSame('test.cog', 'test.out') 1682 | 1683 | def testEmptySuffix(self): 1684 | d = { 1685 | 'test.cog': """\ 1686 | ;[[[cog cog.outl('a\\nb\\nc') ]]] 1687 | ;[[[end]]] 1688 | """, 1689 | 1690 | 'test.out': """\ 1691 | ;[[[cog cog.outl('a\\nb\\nc') ]]] 1692 | a 1693 | b 1694 | c 1695 | ;[[[end]]] 1696 | """, 1697 | } 1698 | 1699 | makeFiles(d) 1700 | self.cog.callableMain(['argv0', '-r', '-s', '', 'test.cog']) 1701 | self.assertFilesSame('test.cog', 'test.out') 1702 | 1703 | def testHellishSuffix(self): 1704 | d = { 1705 | 'test.cog': """\ 1706 | ;[[[cog cog.outl('a\\n\\nb') ]]] 1707 | """, 1708 | 1709 | 'test.out': """\ 1710 | ;[[[cog cog.outl('a\\n\\nb') ]]] 1711 | a /\\n*+([)]>< 1712 | 1713 | b /\\n*+([)]>< 1714 | """, 1715 | } 1716 | 1717 | makeFiles(d) 1718 | self.cog.callableMain(['argv0', '-z', '-r', '-s', r' /\n*+([)]><', 'test.cog']) 1719 | self.assertFilesSame('test.cog', 'test.out') 1720 | 1721 | 1722 | class WritabilityTests(TestCaseWithTempDir): 1723 | 1724 | d = { 1725 | 'test.cog': """\ 1726 | //[[[cog 1727 | for fn in ['DoSomething', 'DoAnotherThing', 'DoLastThing']: 1728 | cog.outl("void %s();" % fn) 1729 | //]]] 1730 | //[[[end]]] 1731 | """, 1732 | 1733 | 'test.out': """\ 1734 | //[[[cog 1735 | for fn in ['DoSomething', 'DoAnotherThing', 'DoLastThing']: 1736 | cog.outl("void %s();" % fn) 1737 | //]]] 1738 | void DoSomething(); 1739 | void DoAnotherThing(); 1740 | void DoLastThing(); 1741 | //[[[end]]] 1742 | """, 1743 | } 1744 | 1745 | if os.name == 'nt': #pragma: no cover 1746 | # for Windows 1747 | cmd_w_args = 'attrib -R %s' 1748 | cmd_w_asterisk = 'attrib -R *' 1749 | else: #pragma: no cover 1750 | # for unix-like 1751 | cmd_w_args = 'chmod +w %s' 1752 | cmd_w_asterisk = 'chmod +w *' 1753 | 1754 | def setUp(self): 1755 | TestCaseWithTempDir.setUp(self) 1756 | makeFiles(self.d) 1757 | self.testcog = os.path.join(self.tempdir, 'test.cog') 1758 | os.chmod(self.testcog, stat.S_IREAD) # Make the file readonly. 1759 | assert not os.access(self.testcog, os.W_OK) 1760 | 1761 | def tearDown(self): 1762 | os.chmod(self.testcog, stat.S_IWRITE) # Make the file writable again. 1763 | TestCaseWithTempDir.tearDown(self) 1764 | 1765 | def testReadonlyNoCommand(self): 1766 | with self.assertRaisesRegexp(CogError, "Can't overwrite test.cog"): 1767 | self.cog.callableMain(['argv0', '-r', 'test.cog']) 1768 | assert not os.access(self.testcog, os.W_OK) 1769 | 1770 | def testReadonlyWithCommand(self): 1771 | self.cog.callableMain(['argv0', '-r', '-w', self.cmd_w_args, 'test.cog']) 1772 | self.assertFilesSame('test.cog', 'test.out') 1773 | assert os.access(self.testcog, os.W_OK) 1774 | 1775 | def testReadonlyWithCommandWithNoSlot(self): 1776 | self.cog.callableMain(['argv0', '-r', '-w', self.cmd_w_asterisk, 'test.cog']) 1777 | self.assertFilesSame('test.cog', 'test.out') 1778 | assert os.access(self.testcog, os.W_OK) 1779 | 1780 | def testReadonlyWithIneffectualCommand(self): 1781 | with self.assertRaisesRegexp(CogError, "Couldn't make test.cog writable"): 1782 | self.cog.callableMain(['argv0', '-r', '-w', 'echo %s', 'test.cog']) 1783 | assert not os.access(self.testcog, os.W_OK) 1784 | 1785 | 1786 | class ChecksumTests(TestCaseWithTempDir): 1787 | 1788 | def testCreateChecksumOutput(self): 1789 | d = { 1790 | 'cog1.txt': """\ 1791 | //[[[cog 1792 | cog.outl("This line was generated.") 1793 | //]]] 1794 | This line was generated. 1795 | //[[[end]]] 1796 | This line was not. 1797 | """, 1798 | 1799 | 'cog1.out': """\ 1800 | //[[[cog 1801 | cog.outl("This line was generated.") 1802 | //]]] 1803 | This line was generated. 1804 | //[[[end]]] (checksum: 8adb13fb59b996a1c7f0065ea9f3d893) 1805 | This line was not. 1806 | """, 1807 | } 1808 | 1809 | makeFiles(d) 1810 | self.cog.callableMain(['argv0', '-r', '-c', 'cog1.txt']) 1811 | self.assertFilesSame('cog1.txt', 'cog1.out') 1812 | 1813 | def testCheckChecksumOutput(self): 1814 | d = { 1815 | 'cog1.txt': """\ 1816 | //[[[cog 1817 | cog.outl("This line was newly") 1818 | cog.outl("generated by cog") 1819 | cog.outl("blah blah.") 1820 | //]]] 1821 | This line was generated. 1822 | //[[[end]]] (checksum: 8adb13fb59b996a1c7f0065ea9f3d893) 1823 | """, 1824 | 1825 | 'cog1.out': """\ 1826 | //[[[cog 1827 | cog.outl("This line was newly") 1828 | cog.outl("generated by cog") 1829 | cog.outl("blah blah.") 1830 | //]]] 1831 | This line was newly 1832 | generated by cog 1833 | blah blah. 1834 | //[[[end]]] (checksum: a8540982e5ad6b95c9e9a184b26f4346) 1835 | """, 1836 | } 1837 | 1838 | makeFiles(d) 1839 | self.cog.callableMain(['argv0', '-r', '-c', 'cog1.txt']) 1840 | self.assertFilesSame('cog1.txt', 'cog1.out') 1841 | 1842 | def testRemoveChecksumOutput(self): 1843 | d = { 1844 | 'cog1.txt': """\ 1845 | //[[[cog 1846 | cog.outl("This line was newly") 1847 | cog.outl("generated by cog") 1848 | cog.outl("blah blah.") 1849 | //]]] 1850 | This line was generated. 1851 | //[[[end]]] (checksum: 8adb13fb59b996a1c7f0065ea9f3d893) fooey 1852 | """, 1853 | 1854 | 'cog1.out': """\ 1855 | //[[[cog 1856 | cog.outl("This line was newly") 1857 | cog.outl("generated by cog") 1858 | cog.outl("blah blah.") 1859 | //]]] 1860 | This line was newly 1861 | generated by cog 1862 | blah blah. 1863 | //[[[end]]] fooey 1864 | """, 1865 | } 1866 | 1867 | makeFiles(d) 1868 | self.cog.callableMain(['argv0', '-r', 'cog1.txt']) 1869 | self.assertFilesSame('cog1.txt', 'cog1.out') 1870 | 1871 | def testTamperedChecksumOutput(self): 1872 | d = { 1873 | 'cog1.txt': """\ 1874 | //[[[cog 1875 | cog.outl("This line was newly") 1876 | cog.outl("generated by cog") 1877 | cog.outl("blah blah.") 1878 | //]]] 1879 | Xhis line was newly 1880 | generated by cog 1881 | blah blah. 1882 | //[[[end]]] (checksum: a8540982e5ad6b95c9e9a184b26f4346) 1883 | """, 1884 | 1885 | 'cog2.txt': """\ 1886 | //[[[cog 1887 | cog.outl("This line was newly") 1888 | cog.outl("generated by cog") 1889 | cog.outl("blah blah.") 1890 | //]]] 1891 | This line was newly 1892 | generated by cog 1893 | blah blah! 1894 | //[[[end]]] (checksum: a8540982e5ad6b95c9e9a184b26f4346) 1895 | """, 1896 | 1897 | 'cog3.txt': """\ 1898 | //[[[cog 1899 | cog.outl("This line was newly") 1900 | cog.outl("generated by cog") 1901 | cog.outl("blah blah.") 1902 | //]]] 1903 | 1904 | This line was newly 1905 | generated by cog 1906 | blah blah. 1907 | //[[[end]]] (checksum: a8540982e5ad6b95c9e9a184b26f4346) 1908 | """, 1909 | 1910 | 'cog4.txt': """\ 1911 | //[[[cog 1912 | cog.outl("This line was newly") 1913 | cog.outl("generated by cog") 1914 | cog.outl("blah blah.") 1915 | //]]] 1916 | This line was newly 1917 | generated by cog 1918 | blah blah.. 1919 | //[[[end]]] (checksum: a8540982e5ad6b95c9e9a184b26f4346) 1920 | """, 1921 | 1922 | 'cog5.txt': """\ 1923 | //[[[cog 1924 | cog.outl("This line was newly") 1925 | cog.outl("generated by cog") 1926 | cog.outl("blah blah.") 1927 | //]]] 1928 | This line was newly 1929 | generated by cog 1930 | blah blah. 1931 | extra 1932 | //[[[end]]] (checksum: a8540982e5ad6b95c9e9a184b26f4346) 1933 | """, 1934 | 1935 | 'cog6.txt': """\ 1936 | //[[[cog 1937 | cog.outl("This line was newly") 1938 | cog.outl("generated by cog") 1939 | cog.outl("blah blah.") 1940 | //]]] 1941 | //[[[end]]] (checksum: a8540982e5ad6b95c9e9a184b26f4346) 1942 | """, 1943 | } 1944 | 1945 | makeFiles(d) 1946 | with self.assertRaisesRegexp(CogError, 1947 | r"cog1.txt\(9\): Output has been edited! Delete old checksum to unprotect."): 1948 | self.cog.callableMain(['argv0', '-c', "cog1.txt"]) 1949 | with self.assertRaisesRegexp(CogError, 1950 | r"cog2.txt\(9\): Output has been edited! Delete old checksum to unprotect."): 1951 | self.cog.callableMain(['argv0', '-c', "cog2.txt"]) 1952 | with self.assertRaisesRegexp(CogError, 1953 | r"cog3.txt\(10\): Output has been edited! Delete old checksum to unprotect."): 1954 | self.cog.callableMain(['argv0', '-c', "cog3.txt"]) 1955 | with self.assertRaisesRegexp(CogError, 1956 | r"cog4.txt\(9\): Output has been edited! Delete old checksum to unprotect."): 1957 | self.cog.callableMain(['argv0', '-c', "cog4.txt"]) 1958 | with self.assertRaisesRegexp(CogError, 1959 | r"cog5.txt\(10\): Output has been edited! Delete old checksum to unprotect."): 1960 | self.cog.callableMain(['argv0', '-c', "cog5.txt"]) 1961 | with self.assertRaisesRegexp(CogError, 1962 | r"cog6.txt\(6\): Output has been edited! Delete old checksum to unprotect."): 1963 | self.cog.callableMain(['argv0', '-c', "cog6.txt"]) 1964 | 1965 | def testArgvIsntModified(self): 1966 | argv = ['argv0', '-v'] 1967 | orig_argv = argv[:] 1968 | self.cog.callableMain(argv) 1969 | self.assertEqual(argv, orig_argv) 1970 | 1971 | 1972 | class CustomDelimiterTests(TestCaseWithTempDir): 1973 | 1974 | def testCustomerDelimiters(self): 1975 | d = { 1976 | 'test.cog': """\ 1977 | //{{cog 1978 | cog.outl("void %s();" % "MyFunction") 1979 | //}} 1980 | //{{end}} 1981 | """, 1982 | 1983 | 'test.out': """\ 1984 | //{{cog 1985 | cog.outl("void %s();" % "MyFunction") 1986 | //}} 1987 | void MyFunction(); 1988 | //{{end}} 1989 | """, 1990 | } 1991 | 1992 | makeFiles(d) 1993 | self.cog.callableMain([ 1994 | 'argv0', '-r', 1995 | '--begin-spec={{', '--end-spec=}}', '--end-output={{end}}', 1996 | 'test.cog' 1997 | ]) 1998 | self.assertFilesSame('test.cog', 'test.out') 1999 | 2000 | def testTrulyWackyDelimiters(self): 2001 | # Make sure the delimiters are properly re-escaped. 2002 | d = { 2003 | 'test.cog': """\ 2004 | //**(cog 2005 | cog.outl("void %s();" % "MyFunction") 2006 | //**) 2007 | //**(end)** 2008 | """, 2009 | 2010 | 'test.out': """\ 2011 | //**(cog 2012 | cog.outl("void %s();" % "MyFunction") 2013 | //**) 2014 | void MyFunction(); 2015 | //**(end)** 2016 | """, 2017 | } 2018 | 2019 | makeFiles(d) 2020 | self.cog.callableMain([ 2021 | 'argv0', '-r', 2022 | '--begin-spec=**(', '--end-spec=**)', '--end-output=**(end)**', 2023 | 'test.cog' 2024 | ]) 2025 | self.assertFilesSame('test.cog', 'test.out') 2026 | 2027 | def testChangeJustOneDelimiter(self): 2028 | d = { 2029 | 'test.cog': """\ 2030 | //**(cog 2031 | cog.outl("void %s();" % "MyFunction") 2032 | //]]] 2033 | //[[[end]]] 2034 | """, 2035 | 2036 | 'test.out': """\ 2037 | //**(cog 2038 | cog.outl("void %s();" % "MyFunction") 2039 | //]]] 2040 | void MyFunction(); 2041 | //[[[end]]] 2042 | """, 2043 | } 2044 | 2045 | makeFiles(d) 2046 | self.cog.callableMain([ 2047 | 'argv0', '-r', 2048 | '--begin-spec=**(', 2049 | 'test.cog' 2050 | ]) 2051 | self.assertFilesSame('test.cog', 'test.out') 2052 | 2053 | 2054 | class BlakeTests(TestCaseWithTempDir): 2055 | 2056 | # Blake Winton's contributions. 2057 | def testDeleteCode(self): 2058 | # -o sets the output file. 2059 | d = { 2060 | 'test.cog': """\ 2061 | // This is my C++ file. 2062 | //[[[cog 2063 | fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 2064 | for fn in fnames: 2065 | cog.outl("void %s();" % fn) 2066 | //]]] 2067 | Some Sample Code Here 2068 | //[[[end]]]Data Data 2069 | And Some More 2070 | """, 2071 | 2072 | 'test.out': """\ 2073 | // This is my C++ file. 2074 | void DoSomething(); 2075 | void DoAnotherThing(); 2076 | void DoLastThing(); 2077 | And Some More 2078 | """, 2079 | } 2080 | 2081 | makeFiles(d) 2082 | self.cog.callableMain(['argv0', '-d', '-o', 'test.cogged', 'test.cog']) 2083 | self.assertFilesSame('test.cogged', 'test.out') 2084 | 2085 | def testDeleteCodeWithDashRFails(self): 2086 | d = { 2087 | 'test.cog': """\ 2088 | // This is my C++ file. 2089 | """ 2090 | } 2091 | 2092 | makeFiles(d) 2093 | with self.assertRaises(CogUsageError): 2094 | self.cog.callableMain(['argv0', '-r', '-d', 'test.cog']) 2095 | 2096 | def testSettingGlobals(self): 2097 | # Blake Winton contributed a way to set the globals that will be used in 2098 | # processFile(). 2099 | d = { 2100 | 'test.cog': """\ 2101 | // This is my C++ file. 2102 | //[[[cog 2103 | for fn in fnames: 2104 | cog.outl("void %s();" % fn) 2105 | //]]] 2106 | Some Sample Code Here 2107 | //[[[end]]]""", 2108 | 2109 | 'test.out': """\ 2110 | // This is my C++ file. 2111 | void DoBlake(); 2112 | void DoWinton(); 2113 | void DoContribution(); 2114 | """, 2115 | } 2116 | 2117 | makeFiles(d) 2118 | globals = {} 2119 | globals['fnames'] = ['DoBlake', 'DoWinton', 'DoContribution'] 2120 | self.cog.options.bDeleteCode = True 2121 | self.cog.processFile('test.cog', 'test.cogged', globals=globals) 2122 | self.assertFilesSame('test.cogged', 'test.out') 2123 | 2124 | 2125 | class ErrorCallTests(TestCaseWithTempDir): 2126 | 2127 | def testErrorCallHasNoTraceback(self): 2128 | # Test that cog.error() doesn't show a traceback. 2129 | d = { 2130 | 'error.cog': """\ 2131 | //[[[cog 2132 | cog.error("Something Bad!") 2133 | //]]] 2134 | //[[[end]]] 2135 | """, 2136 | } 2137 | 2138 | makeFiles(d) 2139 | self.cog.main(['argv0', '-r', 'error.cog']) 2140 | output = self.output.getvalue() 2141 | self.assertEqual(output, "Cogging error.cog\nError: Something Bad!\n") 2142 | 2143 | def testRealErrorHasTraceback(self): 2144 | # Test that a genuine error does show a traceback. 2145 | d = { 2146 | 'error.cog': """\ 2147 | //[[[cog 2148 | raise RuntimeError("Hey!") 2149 | //]]] 2150 | //[[[end]]] 2151 | """, 2152 | } 2153 | 2154 | makeFiles(d) 2155 | self.cog.main(['argv0', '-r', 'error.cog']) 2156 | output = self.output.getvalue() 2157 | msg = 'Actual output:\n' + output 2158 | self.assert_(output.startswith("Cogging error.cog\nTraceback (most recent"), msg) 2159 | self.assertIn("RuntimeError: Hey!", output) 2160 | 2161 | 2162 | # Things not yet tested: 2163 | # - A bad -w command (currently fails silently). 2164 | -------------------------------------------------------------------------------- /thirdparty/cogapp/cogapp/test_makefiles.py: -------------------------------------------------------------------------------- 1 | """ Test the cogapp.makefiles modules 2 | http://nedbatchelder.com/code/cog 3 | 4 | Copyright 2004-2015, Ned Batchelder. 5 | """ 6 | 7 | from __future__ import absolute_import 8 | 9 | import unittest # This is a unittest, so this is fundamental. 10 | import shutil, os, random, types, tempfile # We need these modules to write the tests. 11 | 12 | from . import makefiles 13 | 14 | 15 | class SimpleTests(unittest.TestCase): 16 | 17 | def setUp(self): 18 | # Create a temporary directory. 19 | my_dir = 'testmakefiles_tempdir_' + str(random.random())[2:] 20 | self.tempdir = os.path.join(tempfile.gettempdir(), my_dir) 21 | os.mkdir(self.tempdir) 22 | 23 | def tearDown(self): 24 | # Get rid of the temporary directory. 25 | shutil.rmtree(self.tempdir) 26 | 27 | def exists(self, dname, fname): 28 | return os.path.exists(os.path.join(dname, fname)) 29 | 30 | def checkFilesExist(self, d, dname): 31 | for fname in d.keys(): 32 | assert(self.exists(dname, fname)) 33 | if type(d[fname]) == type({}): 34 | self.checkFilesExist(d[fname], os.path.join(dname, fname)) 35 | 36 | def checkFilesDontExist(self, d, dname): 37 | for fname in d.keys(): 38 | assert(not self.exists(dname, fname)) 39 | 40 | def testOneFile(self): 41 | fname = 'foo.txt' 42 | notfname = 'not_here.txt' 43 | d = { fname: "howdy" } 44 | assert(not self.exists(self.tempdir, fname)) 45 | assert(not self.exists(self.tempdir, notfname)) 46 | 47 | makefiles.makeFiles(d, self.tempdir) 48 | assert(self.exists(self.tempdir, fname)) 49 | assert(not self.exists(self.tempdir, notfname)) 50 | 51 | makefiles.removeFiles(d, self.tempdir) 52 | assert(not self.exists(self.tempdir, fname)) 53 | assert(not self.exists(self.tempdir, notfname)) 54 | 55 | def testManyFiles(self): 56 | d = { 57 | 'top1.txt': "howdy", 58 | 'top2.txt': "hello", 59 | 'sub': { 60 | 'sub1.txt': "inside", 61 | 'sub2.txt': "inside2" 62 | }, 63 | } 64 | 65 | self.checkFilesDontExist(d, self.tempdir) 66 | makefiles.makeFiles(d, self.tempdir) 67 | self.checkFilesExist(d, self.tempdir) 68 | makefiles.removeFiles(d, self.tempdir) 69 | self.checkFilesDontExist(d, self.tempdir) 70 | 71 | def testContents(self): 72 | fname = 'bar.txt' 73 | cont0 = "I am bar.txt" 74 | d = { fname: cont0 } 75 | makefiles.makeFiles(d, self.tempdir) 76 | fcont1 = open(os.path.join(self.tempdir, fname)) 77 | assert(fcont1.read() == cont0) 78 | fcont1.close() 79 | 80 | def testDedent(self): 81 | fname = 'dedent.txt' 82 | d = { fname: """\ 83 | This is dedent.txt 84 | \tTabbed in. 85 | spaced in. 86 | OK. 87 | """ 88 | } 89 | makefiles.makeFiles(d, self.tempdir) 90 | fcont = open(os.path.join(self.tempdir, fname)) 91 | assert(fcont.read() == "This is dedent.txt\n\tTabbed in.\n spaced in.\nOK.\n") 92 | fcont.close() 93 | -------------------------------------------------------------------------------- /thirdparty/cogapp/cogapp/test_whiteutils.py: -------------------------------------------------------------------------------- 1 | """ Test the cogapp.whiteutils module. 2 | http://nedbatchelder.com/code/cog 3 | 4 | Copyright 2004-2015, Ned Batchelder. 5 | """ 6 | 7 | from __future__ import absolute_import 8 | 9 | import unittest 10 | 11 | from .whiteutils import * 12 | 13 | 14 | class WhitePrefixTests(unittest.TestCase): 15 | """ Test cases for cogapp.whiteutils. 16 | """ 17 | def testSingleLine(self): 18 | self.assertEqual(whitePrefix(['']), '') 19 | self.assertEqual(whitePrefix([' ']), '') 20 | self.assertEqual(whitePrefix(['x']), '') 21 | self.assertEqual(whitePrefix([' x']), ' ') 22 | self.assertEqual(whitePrefix(['\tx']), '\t') 23 | self.assertEqual(whitePrefix([' x']), ' ') 24 | self.assertEqual(whitePrefix([' \t \tx ']), ' \t \t') 25 | 26 | def testMultiLine(self): 27 | self.assertEqual(whitePrefix([' x',' x',' x']), ' ') 28 | self.assertEqual(whitePrefix([' y',' y',' y']), ' ') 29 | self.assertEqual(whitePrefix([' y',' y',' y']), ' ') 30 | 31 | def testBlankLinesAreIgnored(self): 32 | self.assertEqual(whitePrefix([' x',' x','',' x']), ' ') 33 | self.assertEqual(whitePrefix(['',' x',' x',' x']), ' ') 34 | self.assertEqual(whitePrefix([' x',' x',' x','']), ' ') 35 | self.assertEqual(whitePrefix([' x',' x',' ',' x']), ' ') 36 | 37 | def testTabCharacters(self): 38 | self.assertEqual(whitePrefix(['\timport sys', '', '\tprint sys.argv']), '\t') 39 | 40 | def testDecreasingLengths(self): 41 | self.assertEqual(whitePrefix([' x',' x',' x']), ' ') 42 | self.assertEqual(whitePrefix([' x',' x',' x']), ' ') 43 | 44 | 45 | class ReindentBlockTests(unittest.TestCase): 46 | """ Test cases for cogapp.reindentBlock. 47 | """ 48 | def testNonTermLine(self): 49 | self.assertEqual(reindentBlock(''), '') 50 | self.assertEqual(reindentBlock('x'), 'x') 51 | self.assertEqual(reindentBlock(' x'), 'x') 52 | self.assertEqual(reindentBlock(' x'), 'x') 53 | self.assertEqual(reindentBlock('\tx'), 'x') 54 | self.assertEqual(reindentBlock('x', ' '), ' x') 55 | self.assertEqual(reindentBlock('x', '\t'), '\tx') 56 | self.assertEqual(reindentBlock(' x', ' '), ' x') 57 | self.assertEqual(reindentBlock(' x', '\t'), '\tx') 58 | self.assertEqual(reindentBlock(' x', ' '), ' x') 59 | 60 | def testSingleLine(self): 61 | self.assertEqual(reindentBlock('\n'), '\n') 62 | self.assertEqual(reindentBlock('x\n'), 'x\n') 63 | self.assertEqual(reindentBlock(' x\n'), 'x\n') 64 | self.assertEqual(reindentBlock(' x\n'), 'x\n') 65 | self.assertEqual(reindentBlock('\tx\n'), 'x\n') 66 | self.assertEqual(reindentBlock('x\n', ' '), ' x\n') 67 | self.assertEqual(reindentBlock('x\n', '\t'), '\tx\n') 68 | self.assertEqual(reindentBlock(' x\n', ' '), ' x\n') 69 | self.assertEqual(reindentBlock(' x\n', '\t'), '\tx\n') 70 | self.assertEqual(reindentBlock(' x\n', ' '), ' x\n') 71 | 72 | def testRealBlock(self): 73 | self.assertEqual( 74 | reindentBlock('\timport sys\n\n\tprint sys.argv\n'), 75 | 'import sys\n\nprint sys.argv\n' 76 | ) 77 | 78 | 79 | class CommonPrefixTests(unittest.TestCase): 80 | """ Test cases for cogapp.commonPrefix. 81 | """ 82 | def testDegenerateCases(self): 83 | self.assertEqual(commonPrefix([]), '') 84 | self.assertEqual(commonPrefix(['']), '') 85 | self.assertEqual(commonPrefix(['','','','','']), '') 86 | self.assertEqual(commonPrefix(['cat in the hat']), 'cat in the hat') 87 | 88 | def testNoCommonPrefix(self): 89 | self.assertEqual(commonPrefix(['a','b']), '') 90 | self.assertEqual(commonPrefix(['a','b','c','d','e','f']), '') 91 | self.assertEqual(commonPrefix(['a','a','a','a','a','x']), '') 92 | 93 | def testUsualCases(self): 94 | self.assertEqual(commonPrefix(['ab', 'ac']), 'a') 95 | self.assertEqual(commonPrefix(['aab', 'aac']), 'aa') 96 | self.assertEqual(commonPrefix(['aab', 'aab', 'aab', 'aac']), 'aa') 97 | 98 | def testBlankLine(self): 99 | self.assertEqual(commonPrefix(['abc', 'abx', '', 'aby']), '') 100 | 101 | def testDecreasingLengths(self): 102 | self.assertEqual(commonPrefix(['abcd', 'abc', 'ab']), 'ab') 103 | -------------------------------------------------------------------------------- /thirdparty/cogapp/cogapp/whiteutils.py: -------------------------------------------------------------------------------- 1 | """ Indentation utilities for Cog. 2 | http://nedbatchelder.com/code/cog 3 | 4 | Copyright 2004-2012, Ned Batchelder. 5 | """ 6 | 7 | from __future__ import absolute_import 8 | import re 9 | from .backward import string_types, bytes_types, to_bytes, text_types, b 10 | 11 | def whitePrefix(strings): 12 | """ Determine the whitespace prefix common to all non-blank lines 13 | in the argument list. 14 | """ 15 | # Remove all blank lines from the list 16 | strings = [s for s in strings if s.strip() != ''] 17 | 18 | if not strings: return '' 19 | 20 | # Find initial whitespace chunk in the first line. 21 | # This is the best prefix we can hope for. 22 | pat = r'\s*' 23 | if isinstance(strings[0], bytes_types): 24 | pat = to_bytes(pat) 25 | prefix = re.match(pat, strings[0]).group(0) 26 | 27 | # Loop over the other strings, keeping only as much of 28 | # the prefix as matches each string. 29 | for s in strings: 30 | for i in range(len(prefix)): 31 | if prefix[i] != s[i]: 32 | prefix = prefix[:i] 33 | break 34 | return prefix 35 | 36 | def reindentBlock(lines, newIndent=''): 37 | """ Take a block of text as a string or list of lines. 38 | Remove any common whitespace indentation. 39 | Re-indent using newIndent, and return it as a single string. 40 | """ 41 | sep, nothing = '\n', '' 42 | if isinstance(lines, bytes_types): 43 | sep, nothing = b('\n'), b('') 44 | if isinstance(lines, string_types): 45 | lines = lines.split(sep) 46 | oldIndent = whitePrefix(lines) 47 | outLines = [] 48 | for l in lines: 49 | if oldIndent: 50 | l = l.replace(oldIndent, nothing, 1) 51 | if l and newIndent: 52 | l = newIndent + l 53 | outLines.append(l) 54 | return sep.join(outLines) 55 | 56 | def commonPrefix(strings): 57 | """ Find the longest string that is a prefix of all the strings. 58 | """ 59 | if not strings: 60 | return '' 61 | prefix = strings[0] 62 | for s in strings: 63 | if len(s) < len(prefix): 64 | prefix = prefix[:len(s)] 65 | if not prefix: 66 | return '' 67 | for i in range(len(prefix)): 68 | if prefix[i] != s[i]: 69 | prefix = prefix[:i] 70 | break 71 | return prefix 72 | -------------------------------------------------------------------------------- /thirdparty/gtest/gtest_main.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2006, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | #include 31 | 32 | #include "gtest/gtest.h" 33 | 34 | GTEST_API_ int main(int argc, char **argv) { 35 | printf("Running main() from gtest_main.cc\n"); 36 | testing::InitGoogleTest(&argc, argv); 37 | return RUN_ALL_TESTS(); 38 | } 39 | -------------------------------------------------------------------------------- /thirdparty/sources.txt: -------------------------------------------------------------------------------- 1 | Sources: 2 | 3 | gtest: https://googletest.googlecode.com/files/gtest-1.7.0.zip 4 | 5 | cogapp: forked from https://bitbucket.org/ned/cog , at https://bitbucket.org/hughperkins/cog-standalone Fork justifications: 6 | - added 'q' option, so only shows changed files 7 | - can run standalone, ie you dont have to run 'setup.py' on it before using it 8 | 9 | --------------------------------------------------------------------------------