diff --git a/historic/jam/src/builtins.c b/historic/jam/src/builtins.c index a68cb7322..728937523 100644 --- a/historic/jam/src/builtins.c +++ b/historic/jam/src/builtins.c @@ -239,6 +239,12 @@ load_builtins() bind_builtin( "SORT", builtin_sort, 0, args ); } + + { + char * args[] = { "path", 0 }; + bind_builtin( "NORMALIZE_PATH", + builtin_normalize_path, 0, args ); + } } /* @@ -911,6 +917,96 @@ builtin_sort( PARSE *parse, FRAME *frame ) return list_sort(arg1); } +LIST *builtin_normalize_path( PARSE *parse, FRAME *frame ) +{ + LIST* arg1 = lol_get( frame->args, 0 ); + + /* First, we iterate over all '/'-separated elements, starting from + the end of string. If we see '..', we remove previous path elements. + If we see '.', we remove it. + The removal is done by putting '\1' in the string. After all the string + is processed, we do a second pass, removing '\1' characters. + */ + + string in[1], out[1], tmp[1]; + char* end; /* Last character of the part of string still to be processed. */ + char* current; /* Working pointer. */ + int dotdots = 0; /* Number of '..' elements seen and not processed yet. */ + int rooted = arg1->string[0] == '/'; + + /* Make a copy of input: we should not change it. */ + string_new(in); + if (!rooted) + string_push_back(in, '/'); + string_append(in, arg1->string); + + + end = in->value + in->size - 1; + current = end; + + for(;end >= in->value;) { + /* Set 'current' to the next occurence of '/', which always exists. */ + for(current = end; *current != '/'; --current) + ; + + if (current == end && current != in->value) { + /* Found a trailing slash. Remove it. */ + *current = '\1'; + } else if (current == end && *(current+1) == '/') { + /* Found duplicated slash. Remove it. */ + *current = '\1'; + } else if (end - current == 1 && strncmp(current, "/.", 2) == 0) { + /* Found '/.'. Drop them all. */ + *current = '\1'; + *(current+1) = '\1'; + } else if (end - current == 2 && strncmp(current, "/..", 3) == 0) { + /* Found '/..' */ + *current = '\1'; + *(current+1) = '\1'; + *(current+2) = '\1'; + ++dotdots; + } else if (dotdots) { + char* p = current; + memset(current, '\1', end-current+1); + --dotdots; + } + end = current-1; + } + + + string_new(tmp); + while(dotdots--) + string_append(tmp, "/.."); + string_append(tmp, in->value); + string_copy(in, tmp->value); + string_free(tmp); + + + string_new(out); + /* The resulting path is either empty or has '/' as the first significant + element. If the original path was not rooted, we need to drop first '/'. + If the original path was rooted, and we've got empty path, need to add '/' + */ + if (!rooted) { + current = strchr(in->value, '/'); + if (current) + *current = '\1'; + } + + for (current = in->value; *current; ++current) + if (*current != '\1') + string_push_back(out, *current); + + + char* result = newstr(out->size ? out->value : (rooted ? "/" : ".")); + string_free(in); + string_free(out); + + return list_new(0, result); + +} + + static void lol_build( LOL* lol, char** elements ) { diff --git a/historic/jam/src/builtins.h b/historic/jam/src/builtins.h index 421c54b24..a56c529ef 100644 --- a/historic/jam/src/builtins.h +++ b/historic/jam/src/builtins.h @@ -37,6 +37,7 @@ LIST *builtin_import_module( PARSE *parse, FRAME *args ); LIST *builtin_imported_modules( PARSE *parse, FRAME *frame ); LIST *builtin_instance( PARSE *parse, FRAME *frame ); LIST *builtin_sort( PARSE *parse, FRAME *frame ); +LIST *builtin_normalize_path( PARSE *parse, FRAME *frame ); void backtrace( FRAME *frame ); diff --git a/jam_src/builtins.c b/jam_src/builtins.c index a68cb7322..728937523 100644 --- a/jam_src/builtins.c +++ b/jam_src/builtins.c @@ -239,6 +239,12 @@ load_builtins() bind_builtin( "SORT", builtin_sort, 0, args ); } + + { + char * args[] = { "path", 0 }; + bind_builtin( "NORMALIZE_PATH", + builtin_normalize_path, 0, args ); + } } /* @@ -911,6 +917,96 @@ builtin_sort( PARSE *parse, FRAME *frame ) return list_sort(arg1); } +LIST *builtin_normalize_path( PARSE *parse, FRAME *frame ) +{ + LIST* arg1 = lol_get( frame->args, 0 ); + + /* First, we iterate over all '/'-separated elements, starting from + the end of string. If we see '..', we remove previous path elements. + If we see '.', we remove it. + The removal is done by putting '\1' in the string. After all the string + is processed, we do a second pass, removing '\1' characters. + */ + + string in[1], out[1], tmp[1]; + char* end; /* Last character of the part of string still to be processed. */ + char* current; /* Working pointer. */ + int dotdots = 0; /* Number of '..' elements seen and not processed yet. */ + int rooted = arg1->string[0] == '/'; + + /* Make a copy of input: we should not change it. */ + string_new(in); + if (!rooted) + string_push_back(in, '/'); + string_append(in, arg1->string); + + + end = in->value + in->size - 1; + current = end; + + for(;end >= in->value;) { + /* Set 'current' to the next occurence of '/', which always exists. */ + for(current = end; *current != '/'; --current) + ; + + if (current == end && current != in->value) { + /* Found a trailing slash. Remove it. */ + *current = '\1'; + } else if (current == end && *(current+1) == '/') { + /* Found duplicated slash. Remove it. */ + *current = '\1'; + } else if (end - current == 1 && strncmp(current, "/.", 2) == 0) { + /* Found '/.'. Drop them all. */ + *current = '\1'; + *(current+1) = '\1'; + } else if (end - current == 2 && strncmp(current, "/..", 3) == 0) { + /* Found '/..' */ + *current = '\1'; + *(current+1) = '\1'; + *(current+2) = '\1'; + ++dotdots; + } else if (dotdots) { + char* p = current; + memset(current, '\1', end-current+1); + --dotdots; + } + end = current-1; + } + + + string_new(tmp); + while(dotdots--) + string_append(tmp, "/.."); + string_append(tmp, in->value); + string_copy(in, tmp->value); + string_free(tmp); + + + string_new(out); + /* The resulting path is either empty or has '/' as the first significant + element. If the original path was not rooted, we need to drop first '/'. + If the original path was rooted, and we've got empty path, need to add '/' + */ + if (!rooted) { + current = strchr(in->value, '/'); + if (current) + *current = '\1'; + } + + for (current = in->value; *current; ++current) + if (*current != '\1') + string_push_back(out, *current); + + + char* result = newstr(out->size ? out->value : (rooted ? "/" : ".")); + string_free(in); + string_free(out); + + return list_new(0, result); + +} + + static void lol_build( LOL* lol, char** elements ) { diff --git a/jam_src/builtins.h b/jam_src/builtins.h index 421c54b24..a56c529ef 100644 --- a/jam_src/builtins.h +++ b/jam_src/builtins.h @@ -37,6 +37,7 @@ LIST *builtin_import_module( PARSE *parse, FRAME *args ); LIST *builtin_imported_modules( PARSE *parse, FRAME *frame ); LIST *builtin_instance( PARSE *parse, FRAME *frame ); LIST *builtin_sort( PARSE *parse, FRAME *frame ); +LIST *builtin_normalize_path( PARSE *parse, FRAME *frame ); void backtrace( FRAME *frame ); diff --git a/kernel/modules.jam b/kernel/modules.jam index 5ccf2fb56..9a4fdc827 100755 --- a/kernel/modules.jam +++ b/kernel/modules.jam @@ -200,44 +200,7 @@ local rule normalize-raw-paths ( paths * ) local result ; for p in $(paths:T) { - local elements ; # the simplified path elements - local dotdots ; # a stack of .. elements we've seen - - # consume the last path element in p until there's nothing - # left - while $(p) - { - local split = [ MATCH ^(.+)/(.*)$ : $(p) ] ; - - if ! $(split) - { # no further slashes; prepend the last element - elements = $(p) $(elements) ; - } - else if $(split[2]) = . - { # ignore the identity directory - } - else if $(split[2]) = .. - { # prepare to absorb the next named element - dotdots += .. ; # push parent onto stack - } - else if $(dotdots) - { # absorb a named element - dotdots = $(dotdots[2-]) ; # pop parent off stack - } - else if $(split[2]) # if the element was empty, ignore it - { # to drop a trailing slash - elements = $(split[2]) $(elements) ; - } - # drop the last element of p - p = $(split[1]) ; - } - - # prepend any unused parent directories - elements = $(dotdots) $(elements) ; - # If everything cancelled out, it's the identity directory. - elements ?= . ; - - result += $(elements:J=/) ; + result += [ NORMALIZE_PATH $(p) ] ; } return $(result) ; } @@ -355,10 +318,12 @@ local rule __test__ ( ) assert.result . : normalize-raw-paths . ; assert.result .. : normalize-raw-paths .. ; assert.result ../.. : normalize-raw-paths ../.. ; + assert.result .. : normalize-raw-paths ./.. ; assert.result / / : normalize-raw-paths / \\ ; assert.result a : normalize-raw-paths a ; assert.result a : normalize-raw-paths a/ ; assert.result /a : normalize-raw-paths /a/ ; + assert.result / : normalize-raw-paths /a/.. ; } diff --git a/new/path.jam b/new/path.jam index f35a434d1..dedb30d5e 100644 --- a/new/path.jam +++ b/new/path.jam @@ -140,64 +140,7 @@ rule reverse ( path ) # local rule join-imp ( elements + ) { - local path1 = $(elements[1]) ; - local path2 = $(elements[2]) ; - - if $(elements[3-]) - { - path2 = [ join $(elements[2-]) ] ; - } - if $(path1) = . - { - return $(path2) ; - } - else if $(path1) = / - { - return /$(path2) ; - } - else if $(path2) = . - { - return $(path1) ; - } - else - { - # Separate the part with ".." from the rest. - local parts = [ regex.match "(\\.\\.(/\\.\\.)*)?/?(.*)" : $(path2) : 1 3 ] ; - - if $(parts[1]) - { - local up_tokens = [ regex.split $(parts[1]) "/" ] ; - for local i in $(up_tokens) - { - path1 = [ parent $(path1) ] ; - } - } - - if $(path1) = . - { - if $(parts[2]) - { - return $(parts[2]) ; - } - else - { - return . ; - } - } - else if $(path1) = / - { - # TODO: consider if it's possible to have empty $(parts[2]) here. - return /$(parts[2]) ; - } - else if $(parts[2]) - { - return $(path1)/$(parts[2]) ; - } - else - { - return $(path1) ; - } - } + return [ NORMALIZE_PATH $(elements:J="/") ] ; } # @@ -405,7 +348,7 @@ rule make-UNIX ( native ) } else { - return [ path.join [ regex.split $(native) "/" ] ] ; + return [ NORMALIZE_PATH $(native:T) ] ; } } diff --git a/v2/kernel/modules.jam b/v2/kernel/modules.jam index 5ccf2fb56..9a4fdc827 100755 --- a/v2/kernel/modules.jam +++ b/v2/kernel/modules.jam @@ -200,44 +200,7 @@ local rule normalize-raw-paths ( paths * ) local result ; for p in $(paths:T) { - local elements ; # the simplified path elements - local dotdots ; # a stack of .. elements we've seen - - # consume the last path element in p until there's nothing - # left - while $(p) - { - local split = [ MATCH ^(.+)/(.*)$ : $(p) ] ; - - if ! $(split) - { # no further slashes; prepend the last element - elements = $(p) $(elements) ; - } - else if $(split[2]) = . - { # ignore the identity directory - } - else if $(split[2]) = .. - { # prepare to absorb the next named element - dotdots += .. ; # push parent onto stack - } - else if $(dotdots) - { # absorb a named element - dotdots = $(dotdots[2-]) ; # pop parent off stack - } - else if $(split[2]) # if the element was empty, ignore it - { # to drop a trailing slash - elements = $(split[2]) $(elements) ; - } - # drop the last element of p - p = $(split[1]) ; - } - - # prepend any unused parent directories - elements = $(dotdots) $(elements) ; - # If everything cancelled out, it's the identity directory. - elements ?= . ; - - result += $(elements:J=/) ; + result += [ NORMALIZE_PATH $(p) ] ; } return $(result) ; } @@ -355,10 +318,12 @@ local rule __test__ ( ) assert.result . : normalize-raw-paths . ; assert.result .. : normalize-raw-paths .. ; assert.result ../.. : normalize-raw-paths ../.. ; + assert.result .. : normalize-raw-paths ./.. ; assert.result / / : normalize-raw-paths / \\ ; assert.result a : normalize-raw-paths a ; assert.result a : normalize-raw-paths a/ ; assert.result /a : normalize-raw-paths /a/ ; + assert.result / : normalize-raw-paths /a/.. ; } diff --git a/v2/util/path.jam b/v2/util/path.jam index f35a434d1..dedb30d5e 100644 --- a/v2/util/path.jam +++ b/v2/util/path.jam @@ -140,64 +140,7 @@ rule reverse ( path ) # local rule join-imp ( elements + ) { - local path1 = $(elements[1]) ; - local path2 = $(elements[2]) ; - - if $(elements[3-]) - { - path2 = [ join $(elements[2-]) ] ; - } - if $(path1) = . - { - return $(path2) ; - } - else if $(path1) = / - { - return /$(path2) ; - } - else if $(path2) = . - { - return $(path1) ; - } - else - { - # Separate the part with ".." from the rest. - local parts = [ regex.match "(\\.\\.(/\\.\\.)*)?/?(.*)" : $(path2) : 1 3 ] ; - - if $(parts[1]) - { - local up_tokens = [ regex.split $(parts[1]) "/" ] ; - for local i in $(up_tokens) - { - path1 = [ parent $(path1) ] ; - } - } - - if $(path1) = . - { - if $(parts[2]) - { - return $(parts[2]) ; - } - else - { - return . ; - } - } - else if $(path1) = / - { - # TODO: consider if it's possible to have empty $(parts[2]) here. - return /$(parts[2]) ; - } - else if $(parts[2]) - { - return $(path1)/$(parts[2]) ; - } - else - { - return $(path1) ; - } - } + return [ NORMALIZE_PATH $(elements:J="/") ] ; } # @@ -405,7 +348,7 @@ rule make-UNIX ( native ) } else { - return [ path.join [ regex.split $(native) "/" ] ] ; + return [ NORMALIZE_PATH $(native:T) ] ; } }