Added support for wildcard matching (r391:396)

This commit is contained in:
starkos 2008-05-27 18:47:25 +00:00
parent ee5b93015c
commit bce9ab296c
22 changed files with 447 additions and 54 deletions

View File

@ -28,7 +28,7 @@ int gmake_project_shell_detect(Session sess, Project prj, Stream strm)
z |= stream_writeline(strm, "ifeq (,$(ComSpec)$(COMSPEC))"); z |= stream_writeline(strm, "ifeq (,$(ComSpec)$(COMSPEC))");
z |= stream_writeline(strm, " SHELLTYPE := posix"); z |= stream_writeline(strm, " SHELLTYPE := posix");
z |= stream_writeline(strm, "endif"); z |= stream_writeline(strm, "endif");
z |= stream_writeline(strm, "ifeq (/bin/sh.exe,$(SHELL))"); z |= stream_writeline(strm, "ifeq (/bin,$(findstring /bin,$(SHELL)))");
z |= stream_writeline(strm, " SHELLTYPE := posix"); z |= stream_writeline(strm, " SHELLTYPE := posix");
z |= stream_writeline(strm, "endif"); z |= stream_writeline(strm, "endif");
z |= stream_writeline(strm, ""); z |= stream_writeline(strm, "");

View File

@ -223,8 +223,11 @@ int make_project_objects(Session sess, Project prj, Stream strm)
for (i = 0; i < n; ++i) for (i = 0; i < n; ++i)
{ {
const char* filename = strings_item(files, i); const char* filename = strings_item(files, i);
const char* obj_name = make_get_obj_filename(filename); if (path_is_cpp_source(filename))
z |= stream_writeline(strm, "\t%s \\", obj_name); {
const char* obj_name = make_get_obj_filename(filename);
z |= stream_writeline(strm, "\t%s \\", obj_name);
}
} }
z |= stream_writeline(strm, ""); z |= stream_writeline(strm, "");
@ -288,11 +291,14 @@ int make_project_source_rules(Session sess, Project prj, Stream strm)
for (i = 0; i < n; ++i) for (i = 0; i < n; ++i)
{ {
const char* filename = strings_item(files, i); const char* filename = strings_item(files, i);
const char* obj_name = make_get_obj_filename(filename); if (path_is_cpp_source(filename))
z |= stream_writeline(strm, "%s: %s", obj_name, filename); {
z |= stream_writeline(strm, "\t@echo $(notdir $<)"); const char* obj_name = make_get_obj_filename(filename);
z |= stream_writeline(strm, "\t@$(CXX) $(CXXFLAGS) -o $@ -c $<"); z |= stream_writeline(strm, "%s: %s", obj_name, filename);
z |= stream_writeline(strm, ""); z |= stream_writeline(strm, "\t@echo $(notdir $<)");
z |= stream_writeline(strm, "\t@$(CXX) $(CXXFLAGS) -o $@ -c $<");
z |= stream_writeline(strm, "");
}
} }
return z; return z;
@ -308,7 +314,7 @@ int make_project_target(Session sess, Project prj, Stream strm)
UNUSED(sess); UNUSED(sess);
z |= stream_writeline(strm, "$(OUTFILE): $(OUTDIR) $(OBJDIR) $(OBJECTS) $(LDDEPS) $(RESOURCES)"); z |= stream_writeline(strm, "$(OUTFILE): $(OUTDIR) $(OBJDIR) $(OBJECTS) $(LDDEPS) $(RESOURCES)");
z |= stream_writeline(strm, "\t@echo Linking %s", project_get_name(prj)); z |= stream_writeline(strm, "\t@echo Linking %s", project_get_name(prj));
z |= stream_writeline(strm, "\t$(CXX) -o $@ $(LDFLAGS) $(ARCHFLAGS) $(OBJECTS) $(RESOURCES)"); z |= stream_writeline(strm, "\t@$(CXX) -o $@ $(LDFLAGS) $(ARCHFLAGS) $(OBJECTS) $(RESOURCES)");
z |= stream_writeline(strm, ""); z |= stream_writeline(strm, "");
return z; return z;
} }

View File

@ -169,7 +169,7 @@ SUITE(action)
CHECK_EQUAL( CHECK_EQUAL(
"$(OUTFILE): $(OUTDIR) $(OBJDIR) $(OBJECTS) $(LDDEPS) $(RESOURCES)\n" "$(OUTFILE): $(OUTDIR) $(OBJDIR) $(OBJECTS) $(LDDEPS) $(RESOURCES)\n"
"\t@echo Linking MyProject\n" "\t@echo Linking MyProject\n"
"\t$(CXX) -o $@ $(LDFLAGS) $(ARCHFLAGS) $(OBJECTS) $(RESOURCES)\n" "\t@$(CXX) -o $@ $(LDFLAGS) $(ARCHFLAGS) $(OBJECTS) $(RESOURCES)\n"
"\n", "\n",
buffer); buffer);
} }

View File

@ -13,6 +13,18 @@
#include "base/cstr.h" #include "base/cstr.h"
/**
* Determines if the sequence appears anywhere in the target string.
* \param str The string to test.
* \param expected The sequence to search for.
* \returns True if the sequence is contained in the string.
*/
int cstr_contains(const char* str, const char* expected)
{
return (strstr(str, expected) != NULL);
}
/** /**
* Determines if the string ends with a particular sequence. * Determines if the string ends with a particular sequence.
* \param str The string to test. * \param str The string to test.

View File

@ -13,6 +13,7 @@
#if !defined(PREMAKE_CSTR_H) #if !defined(PREMAKE_CSTR_H)
#define PREMAKE_CSTR_H #define PREMAKE_CSTR_H
int cstr_contains(const char* str, const char* expected);
int cstr_ends_with(const char* str, const char* expected); int cstr_ends_with(const char* str, const char* expected);
int cstr_eq(const char* str, const char* expected); int cstr_eq(const char* str, const char* expected);
int cstr_eqi(const char* str, const char* expected); int cstr_eqi(const char* str, const char* expected);

View File

@ -10,7 +10,7 @@
#include "base/string.h" #include "base/string.h"
DEFINE_CLASS(string) DEFINE_CLASS(String)
{ {
char* contents; char* contents;
int capacity; int capacity;
@ -22,11 +22,11 @@ DEFINE_CLASS(string)
* \param value The C string value. * \param value The C string value.
* \returns A new dynamic string object containing a copy of the string. * \returns A new dynamic string object containing a copy of the string.
*/ */
string string_create(const char* value) String string_create(const char* value)
{ {
if (value != NULL) if (value != NULL)
{ {
string str = ALLOC_CLASS(string); String str = ALLOC_CLASS(String);
str->capacity = strlen(value) + 1; str->capacity = strlen(value) + 1;
str->contents = (char*)malloc(str->capacity); str->contents = (char*)malloc(str->capacity);
strcpy(str->contents, value); strcpy(str->contents, value);
@ -43,7 +43,7 @@ string string_create(const char* value)
* Destroy a dynamic string object and free the associated memory. * Destroy a dynamic string object and free the associated memory.
* \param str The string to destroy. * \param str The string to destroy.
*/ */
void string_destroy(string str) void string_destroy(String str)
{ {
if (str != NULL) if (str != NULL)
{ {
@ -58,7 +58,7 @@ void string_destroy(string str)
* \param str The string to query. * \param str The string to query.
* \returns The C string value. * \returns The C string value.
*/ */
const char* string_cstr(string str) const char* string_cstr(String str)
{ {
if (str != NULL) if (str != NULL)
return str->contents; return str->contents;

View File

@ -13,11 +13,11 @@
#if !defined(PREMAKE_STRING_H) #if !defined(PREMAKE_STRING_H)
#define PREMAKE_STRING_H #define PREMAKE_STRING_H
DECLARE_CLASS(string); DECLARE_CLASS(String);
string string_create(const char* value); String string_create(const char* value);
void string_destroy(string str); void string_destroy(String str);
const char* string_cstr(string str); const char* string_cstr(String str);
#endif #endif
/** @} */ /** @} */

View File

@ -12,6 +12,21 @@ extern "C" {
SUITE(cstr) SUITE(cstr)
{ {
/**************************************************************************
* cstr_contains() tests
**************************************************************************/
TEST(CStrContains_ReturnsTrue_OnMatch)
{
CHECK(cstr_contains("Abcdef", "cd"));
}
TEST(CStrContains_ReturnsFalse_OnMismatch)
{
CHECK(!cstr_contains("Abcdef", "xy"));
}
/************************************************************************** /**************************************************************************
* cstr_ends_with() tests * cstr_ends_with() tests
**************************************************************************/ **************************************************************************/

View File

@ -18,7 +18,7 @@ SUITE(base)
TEST(StringCreate_ReturnsNull_OnNull) TEST(StringCreate_ReturnsNull_OnNull)
{ {
string str = string_create(NULL); String str = string_create(NULL);
CHECK(str == NULL); CHECK(str == NULL);
} }

View File

@ -38,6 +38,8 @@ enum Platform
#define PLATFORM_WINDOWS (1) #define PLATFORM_WINDOWS (1)
#endif #endif
DECLARE_CLASS(PlatformSearch)
/** /**
* Create a directory, if it doesn't exist already. * Create a directory, if it doesn't exist already.
@ -76,6 +78,37 @@ int platform_dir_set_current(const char* path);
enum Platform platform_get(void); enum Platform platform_get(void);
/**
* Create a new platform file search context.
*/
PlatformSearch platform_search_create(const char* mask);
/**
* Destroy a platform search context.
*/
void platform_search_destroy(PlatformSearch search);
/**
* Retrieve the name of the current match in the search.
*/
const char* platform_search_get_name(PlatformSearch search);
/**
* Determine if the current match is a file or a directory.
*/
int platform_search_is_file(PlatformSearch search);
/**
* Retrieve the next match in a file system search.
* \returns True if another match is available.
*/
int platform_search_next(PlatformSearch search);
/** /**
* Set the platform identification string, forcing a platform-specific * Set the platform identification string, forcing a platform-specific
* behavior regardless of the actual current platform. * behavior regardless of the actual current platform.

View File

@ -6,6 +6,9 @@
#include "premake.h" #include "premake.h"
#include "platform/platform.h" #include "platform/platform.h"
#include "base/path.h"
#include "base/string.h"
#if !defined(PLATFORM_WINDOWS) #if !defined(PLATFORM_WINDOWS)
#include <stdio.h> #include <stdio.h>
@ -18,6 +21,14 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <ctype.h> #include <ctype.h>
DEFINE_CLASS(PlatformSearch)
{
String directory;
String mask;
DIR* handle;
struct dirent* entry;
};
int platform_create_dir(const char* path) int platform_create_dir(const char* path)
{ {
@ -47,5 +58,79 @@ int platform_dir_set_current(const char* path)
} }
PlatformSearch platform_search_create(const char* mask)
{
PlatformSearch search;
const char* dir;
dir = path_directory(mask);
mask = path_filename(mask);
if (strlen(dir) == 0)
{
dir = ".";
}
search = ALLOC_CLASS(PlatformSearch);
search->directory = string_create(dir);
search->mask = string_create(mask);
search->handle = opendir(dir);
search->entry = NULL;
return search;
}
void platform_search_destroy(PlatformSearch search)
{
if (search->handle != NULL)
{
closedir(search->handle);
}
free(search);
}
const char* platform_search_get_name(PlatformSearch search)
{
return search->entry->d_name;
}
int platform_search_is_file(PlatformSearch search)
{
struct stat info;
const char* dir = string_cstr(search->directory);
const char* path = path_join(dir, search->entry->d_name);
if (stat(path, &info) == 0)
{
return S_ISREG(info.st_mode);
}
return 0;
}
int platform_search_next(PlatformSearch search)
{
const char* mask = string_cstr(search->mask);
if (search->handle == NULL)
{
return 0;
}
search->entry = readdir(search->handle);
while (search->entry != NULL)
{
if (fnmatch(mask, search->entry->d_name, 0) == 0)
{
return 1;
}
search->entry = readdir(search->handle);
}
return 0;
}
#endif #endif

View File

@ -4,6 +4,7 @@
* \author Copyright (c) 2002-2008 Jason Perkins and the Premake project * \author Copyright (c) 2002-2008 Jason Perkins and the Premake project
*/ */
#include <stdlib.h>
#include "premake.h" #include "premake.h"
#include "platform/platform.h" #include "platform/platform.h"
#if defined(PLATFORM_WINDOWS) #if defined(PLATFORM_WINDOWS)
@ -12,6 +13,14 @@
#include <windows.h> #include <windows.h>
DEFINE_CLASS(PlatformSearch)
{
HANDLE handle;
int is_first;
WIN32_FIND_DATA entry;
};
int platform_create_dir(const char* path) int platform_create_dir(const char* path)
{ {
return CreateDirectory(path, NULL) ? OKAY : !OKAY; return CreateDirectory(path, NULL) ? OKAY : !OKAY;
@ -44,4 +53,53 @@ int platform_dir_set_current(const char* path)
} }
PlatformSearch platform_search_create(const char* mask)
{
PlatformSearch search = ALLOC_CLASS(PlatformSearch);
search->handle = FindFirstFile(mask, &search->entry);
search->is_first = 1;
return search;
}
void platform_search_destroy(PlatformSearch search)
{
if (search->handle != INVALID_HANDLE_VALUE)
{
FindClose(search->handle);
}
free(search);
}
const char* platform_search_get_name(PlatformSearch search)
{
return search->entry.cFileName;
}
int platform_search_is_file(PlatformSearch search)
{
return (search->entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
}
int platform_search_next(PlatformSearch search)
{
if (search->handle == INVALID_HANDLE_VALUE)
{
return 0;
}
if (search->is_first)
{
search->is_first = 0;
return 1;
}
else
{
return FindNextFile(search->handle, &search->entry);
}
}
#endif #endif

View File

@ -18,7 +18,8 @@
enum FieldKind enum FieldKind
{ {
StringField, StringField,
ListField ListField,
FilesField
}; };
@ -36,7 +37,7 @@ typedef int (*FieldValidator)(const char* value);
struct FieldInfo struct FieldInfo
{ {
const char* name; /**< The name of the field. */ const char* name; /**< The name of the field. */
enum FieldKind kind; /**< StringField or ListField */ enum FieldKind kind; /**< StringField, ListField, etc. */
FieldValidator validator; /**< The field validation function */ FieldValidator validator; /**< The field validation function */
}; };

View File

@ -20,7 +20,7 @@
struct FieldInfo ProjectFieldInfo[] = struct FieldInfo ProjectFieldInfo[] =
{ {
{ "basedir", StringField, NULL }, { "basedir", StringField, NULL },
{ "files", ListField, NULL }, { "files", FilesField, NULL },
{ "guid", StringField, guid_is_valid }, { "guid", StringField, guid_is_valid },
{ "language", StringField, project_is_valid_language }, { "language", StringField, project_is_valid_language },
{ "location", StringField, NULL }, { "location", StringField, NULL },

View File

@ -16,7 +16,7 @@ static int fn_accessor_register(lua_State* L, struct FieldInfo* fields);
static int fn_accessor_register_field(lua_State* L, struct FieldInfo* field); static int fn_accessor_register_field(lua_State* L, struct FieldInfo* field);
static int fn_accessor_set_string_value(lua_State* L, struct FieldInfo* field); static int fn_accessor_set_string_value(lua_State* L, struct FieldInfo* field);
static int fn_accessor_set_list_value(lua_State* L, struct FieldInfo* field); static int fn_accessor_set_list_value(lua_State* L, struct FieldInfo* field);
static void fn_accessor_append_values(lua_State* L, int destIndex, int srcIndex); static void fn_accessor_append_value(lua_State* L, struct FieldInfo* field, int tbl, int idx);
/** /**
@ -177,17 +177,8 @@ static int fn_accessor_set_list_value(lua_State* L, struct FieldInfo* field)
/* get the current value of the field */ /* get the current value of the field */
lua_getfield(L, -1, field->name); lua_getfield(L, -1, field->name);
/* if the value passed in is a table, append all of its contents to the current /* move the values into the field */
* field value. If the value passed in is a single string, add it at the end */ fn_accessor_append_value(L, field, lua_gettop(L), 1);
if (lua_istable(L, 1))
{
fn_accessor_append_values(L, lua_gettop(L), 1);
}
else
{
lua_pushvalue(L, 1);
lua_rawseti(L, -2, luaL_getn(L, -2) + 1);
}
/* remove the field value from the stack */ /* remove the field value from the stack */
lua_pop(L, 1); lua_pop(L, 1);
@ -196,26 +187,43 @@ static int fn_accessor_set_list_value(lua_State* L, struct FieldInfo* field)
/** /**
* Copy values from one table to the end of another table. * Append a value to table. If the value is itself a table, it is "flattened" into the
* \param destIndex The absolute stack index of the destination table. * destination table by iterating over each of its items and adding each in turn to the
* \param srcIndex The absolute stack index of the source table. * target table.
* \param L The current Lua state.
* \param field A description of the field being populated.
* \param tbl The table to contain the values.
* \param idx The value to add to the table.
*/ */
static void fn_accessor_append_values(lua_State* L, int destIndex, int srcIndex) static void fn_accessor_append_value(lua_State* L, struct FieldInfo* field, int tbl, int idx)
{ {
int i; int i, n;
int src_n = luaL_getn(L, srcIndex); if (lua_istable(L, idx))
int dst_n = luaL_getn(L, destIndex);
for (i = 1; i <= src_n; ++i)
{ {
lua_rawgeti(L, srcIndex, i); n = luaL_getn(L, idx);
if (lua_istable(L, -1)) for (i = 1; i <= n; ++i)
{ {
fn_accessor_append_values(L, destIndex, lua_gettop(L)); lua_rawgeti(L, idx, i);
dst_n = luaL_getn(L, destIndex); fn_accessor_append_value(L, field, tbl, lua_gettop(L));
}
lua_pop(L, 1);
}
else
{
/* if this field contains files, check for and expand wildcards by calling match() */
const char* value = lua_tostring(L, -1);
if (field->kind == FilesField && cstr_contains(value, "*"))
{
lua_getglobal(L, "match");
lua_pushvalue(L, -2);
lua_call(L, 1, 1);
fn_accessor_append_value(L, field, tbl, lua_gettop(L));
lua_pop(L, 1);
} }
else else
{ {
lua_rawseti(L, destIndex, ++dst_n); n = luaL_getn(L, tbl);
lua_rawseti(L, tbl, n + 1);
} }
} }
} }

View File

@ -21,8 +21,8 @@ int fn_dofile(lua_State* L)
const char *filename; const char *filename;
const char* full_path; const char* full_path;
const char* script_dir; const char* script_dir;
string old_file; String old_file;
string old_working_dir; String old_working_dir;
int top, result; int top, result;
filename = luaL_checkstring(L, 1); filename = luaL_checkstring(L, 1);

101
src/script/fn_match.c Normal file
View File

@ -0,0 +1,101 @@
/**
* \file fn_match.c
* \brief Perform a wildcard match for files.
* \author Copyright (c) 2008 Jason Perkins and the Premake project
*/
#include "premake.h"
#include "script_internal.h"
#include "platform/platform.h"
#include "base/cstr.h"
#include "base/path.h"
#include "base/string.h"
static void do_scan(lua_State* L, const char* mask);
/**
* Perform a wildcard match for files; returns a table of file names which
* match the supplied pattern.
*/
int fn_match(lua_State* L)
{
int i, n;
/* table to hold the results */
lua_newtable(L);
/* scan each mask in the provided list */
n = lua_gettop(L);
for (i = 1; i < n; ++i)
{
const char* mask = luaL_checkstring(L, i);
do_scan(L, mask);
}
return 1;
}
/**
* Does the real work of scanning the file system and matching the supplied patterns.
*/
void do_scan(lua_State* L, const char* mask)
{
/* mark the end of the results lists so I know where to add new entries */
int n = luaL_getn(L, -1);
/* the search will only return file names; remember the path so I can add it back */
String dir = string_create(path_directory(mask));
/* search */
PlatformSearch search = platform_search_create(mask);
while (platform_search_next(search))
{
const char* filename = platform_search_get_name(search);
int is_file = platform_search_is_file(search);
if (is_file)
{
/* add it to the results */
const char* path = path_join(string_cstr(dir), filename);
lua_pushstring(L, path);
lua_rawseti(L, -2, ++n);
}
}
platform_search_destroy(search);
/* if the mask uses the ** pattern, recurse into subdirectories */
if (cstr_contains(mask, "**"))
{
mask = path_filename(mask);
/* look for subdirectories */
search = platform_search_create(path_join(string_cstr(dir), "*"));
while (platform_search_next(search))
{
if (!platform_search_is_file(search))
{
const char* dirname = platform_search_get_name(search);
if (dirname[0] != '.')
{
/* build a new mask from the original directory, this new subdirectory,
* and the original search mask. Need to put it in a string to ensure
* its buffer doesn't get overwritten */
String subsearch;
const char* path = path_join(string_cstr(dir), dirname);
path = path_join(path, mask);
subsearch = string_create(path);
/* recurse to search this subdirectory */
do_scan(L, string_cstr(subsearch));
string_destroy(subsearch);
}
}
}
platform_search_destroy(search);
}
string_destroy(dir);
}

View File

@ -15,9 +15,10 @@
/** Functions to add to the global namespace */ /** Functions to add to the global namespace */
static const luaL_Reg global_funcs[] = { static const luaL_Reg global_funcs[] = {
{ "dofile", fn_dofile }, { "dofile", fn_dofile },
{ "include", fn_include }, { "include", fn_include },
{ "project", fn_project }, { "match", fn_match },
{ "project", fn_project },
{ "solution", fn_solution }, { "solution", fn_solution },
{ NULL, NULL } { NULL, NULL }
}; };

View File

@ -159,7 +159,7 @@ void script_internal_populate_object(lua_State* L, struct FieldInfo* fields)
/* set all list-type configuration values to empty tables */ /* set all list-type configuration values to empty tables */
for (field = fields; field->name != NULL; ++field) for (field = fields; field->name != NULL; ++field)
{ {
if (field->kind == ListField) if (field->kind != StringField)
{ {
lua_newtable(L); lua_newtable(L);
lua_setfield(L, -2, field->name); lua_setfield(L, -2, field->name);

View File

@ -52,6 +52,7 @@ int fn_dofile(lua_State* L);
int fn_error(lua_State* L); int fn_error(lua_State* L);
int fn_getcwd(lua_State* L); int fn_getcwd(lua_State* L);
int fn_include(lua_State* L); int fn_include(lua_State* L);
int fn_match(lua_State* L);
int fn_project(lua_State* L); int fn_project(lua_State* L);
int fn_solution(lua_State* L); int fn_solution(lua_State* L);

View File

@ -117,4 +117,17 @@ SUITE(script)
"return (prj.files[1] == 'Hello.c' and prj.files[2] == 'Goodbye.c')" ); "return (prj.files[1] == 'Hello.c' and prj.files[2] == 'Goodbye.c')" );
CHECK_EQUAL("true", result); CHECK_EQUAL("true", result);
} }
/**************************************************************************
* List field tests
**************************************************************************/
TEST_FIXTURE(FxAccessor, Accessor_ExpandsWildcards)
{
const char* result = script_run_string(script,
"files { 'testing/test_files/*.lua' };"
"return (#prj.files > 0)");
CHECK_EQUAL("true", result);
}
} }

View File

@ -0,0 +1,58 @@
/**
* \file fn_match_tests.cpp
* \brief Automated test for the match() function.
* \author Copyright (c) 2008 Jason Perkins and the Premake project
*/
#include "premake.h"
#include "script_tests.h"
extern "C" {
}
struct FxMatch : FxScript
{
FxMatch()
{
script_run_string(script,
"function contains(tbl,val)"
" for i,v in ipairs(files) do"
" if (v == val) then return true end"
" end"
" return false;"
"end");
};
};
SUITE(script)
{
TEST_FIXTURE(FxMatch, Match_Exists_OnStartup)
{
const char* result = script_run_string(script,
"return (match ~= nil)");
CHECK_EQUAL("true", result);
}
TEST_FIXTURE(FxMatch, Match_ReturnsEmptyTable_OnNoMatches)
{
const char* result = script_run_string(script,
"return #match('*.xyz')");
CHECK_EQUAL("0", result);
}
TEST_FIXTURE(FxMatch, Match_ReturnsMatches_OnMatch)
{
const char* result = script_run_string(script,
"files = match('testing/test_files/*.lua');"
"return contains(files, 'testing/test_files/true.lua');");
CHECK_EQUAL("true", result);
}
TEST_FIXTURE(FxMatch, Match_Recurses_OnDoubleStar)
{
const char* result = script_run_string(script,
"files = match('testing/test_files/**.lua');"
"return contains(files, 'testing/test_files/nested/getcwd.lua');");
CHECK_EQUAL("true", result);
}
}