Re: [quickbook] Direct code import...

Daniel James <[email protected]>
Newsgroups gmane.comp.lib.boost.documentation
Message-ID <CAHOE3ycgPo_KwobvLE3y9UKxFHuWVYk0Md1xXxOBHLYxj_DGTg@mail.gmail.com>
On 5 January 2013 21:27, Rene Rivera <[email protected]> wrote:
> After having my Mac's drive get trashed I lost a set of notes I had on what
> needed to get finished for this. Does anyone have an idea on what needs to
> get done to complete this feature on boostbook-dev branch?

I'm not sure. The quickbook-dev branch was completely merged to trunk,
but I reverted the glob feature because it didn't completely work. One
problem was that it uses a bitset for all the possible character
values, which is fine when using 8-bit characters, but not when using
16-bit characters (on windows). There might have been others, I can't
remember.

At the time I wanted to support unicode, but since then I've found out
it's trickier than I thought, because different filesystems use
different normalisation forms, or more typically no normalisation at
all. Which makes consistent cross platform handling of unicode a bit
tricky. For example in HFS+ 'é' is actually two code points. So a glob
which is aware of UTF-8, but not aware of combining characters, won't
match 'café.txt' with 'caf?.txt', or even 'ca?é.txt' (that's on HFS+),
but will for other filesystems. I had hoped Boost.Locale might help
out here, but I think it requires ICU for normalisation, which is IMO
far too large a dependency.

So... for the time being, it'd probably be best if the glob only
supports ascii. If the glob contains non-ascii characters (i.e.
anything > 127), it should be an error, if a file does, it should be
ignored (actually could allow '*' to match non-ascii characters). This
won't be that much of a problem since I expect most people don't
really expect unicode filenames to work anyway (GNU find's glob has
the same issue, although interestingly, GNU bash has fixed it in
recent years).

I've attached an updated patch to restore the glob implementation.
Since the quickbook-dev branch was merged, I added support for
tracking dependencies, which the glob implementation doesn't do. I
will probably need to rework the dependency tracking a bit as it
combines checking for a file's existence with tracking (i.e. every
time quickbook looks to see if a file exists, it does it through the
dependency tracker so that it will be recorded). I think I'll need to
add glob support to the dependency tracker. Probably don't need to
implement the glob in the glob in the dependency tracker, but just
record it as a special type of checked path.

Since no new quickbook developments are going to be included in the
new boost release, and the next will use git, it probably makes sense
to switch to using git now. I'm currently running git-svn to create a
new repo, but it won't be the proper one (it's missing some branches,
and I haven't mapped the users, and probably other issues, the kde
svn2git might work better). It should be possible to copy new changes
over to the final repo, so it'll be a good place to start.

_______________________________________________
Boost-docs mailing list
[email protected]
http://lists.boost.org/mailman/listinfo.cgi/boost-docs
glob.patch (application/octet-stream, 15.7 KB)
diff --git a/tools/quickbook/doc/1_6.qbk b/tools/quickbook/doc/1_6.qbk
index 89c41f2..4f40332 100644
--- a/tools/quickbook/doc/1_6.qbk
+++ b/tools/quickbook/doc/1_6.qbk
@@ -282,6 +282,26 @@ html.
 
 [section:1_7 Quickbook 1.7]
 
+[section:glob Including multiple files with Globs]
+
+One can now include multiple files at once using a glob pattern for the
+file reference:
+
+    [include sub/*/*.qbk]
+    [include include/*.h]
+
+All the matching files, and intermediate irectories, will match and be
+included. The glob pattern can be "\*" for matching zero or more characters,
+"?" for matching a single character, "\[<c>-<c>\]" to match a character class,
+"\[\^<char>-<char>\]" to exclusive match a character class, "\\\\" to escape
+a glob special character which is then matched, and anything else is matched
+to the character.
+
+[note Because of the escaping in file references the "\\\\" glob escape is
+a double "\\"; i.e. and escaped back-slash.]
+
+[endsect]
+
 [section:source_mode Source mode for single entities]
 
 1.7 introduces a new `!` element type for setting the source mode of a single
diff --git a/tools/quickbook/src/actions.cpp b/tools/quickbook/src/actions.cpp
index 304663f..32efc91 100644
--- a/tools/quickbook/src/actions.cpp
+++ b/tools/quickbook/src/actions.cpp
@@ -33,6 +33,7 @@
 #include "block_tags.hpp"
 #include "phrase_tags.hpp"
 #include "id_manager.hpp"
+#include "glob.hpp"
 
 namespace quickbook
 {
@@ -1764,8 +1765,8 @@ namespace quickbook
     }
 
     struct path_details {
-        // Will possibly add 'url' and 'glob' to this list later:
-        enum path_type { path };
+        // Will possibly add 'url' to this list later:
+        enum path_type { path, glob };
 
         std::string value;
         path_type type;
@@ -1787,7 +1788,10 @@ namespace quickbook
         std::string path_text = qbk_version_n >= 106u || path.is_encoded() ?
                 path.get_encoded() : path.get_quickbook();
 
-        if(path_text.find('\\') != std::string::npos)
+        bool is_glob = qbk_version_n >= 107u &&
+            path_text.find_first_of("[]?*") != std::string::npos;
+
+        if(!is_glob && path_text.find('\\') != std::string::npos)
         {
             quickbook::detail::ostream* err;
 
@@ -1807,13 +1811,24 @@ namespace quickbook
             boost::replace(path_text, '\\', '/');
         }
 
-        return path_details(path_text, path_details::path);
+        return path_details(path_text,
+            is_glob ? path_details::glob : path_details::path);
     }
 
     xinclude_path calculate_xinclude_path(value const& p, quickbook::state& state)
     {
         path_details details = check_path(p, state);
 
+        if (details.type == path_details::glob) {
+            // TODO: Should know if this is an xinclude or an xmlbase.
+            // Would also help with implementation of 'check_path'.
+            detail::outerr(p.get_file(), p.get_position())
+                << "Glob used in xinclude/xmlbase."
+                << std::endl;
+            ++state.error_count;
+            return xinclude_path(state.current_file->path.parent_path(), "");
+        }
+
         fs::path path = detail::generic_to_path(details.value);
         fs::path full_path = path;
 
@@ -1861,54 +1876,136 @@ namespace quickbook
             }
         };
 
+        #if QUICKBOOK_WIDE_PATHS
+        typedef std::wstring path_string_t;
+        inline path_string_t path_to_string(fs::path const & p)
+        {
+            return p.generic_wstring();
+        }
+        static const path_string_t::value_type* glob_chars = L"[]?*";
+        #else
+        typedef std::string path_string_t;
+        inline path_string_t path_to_string(fs::path const & p)
+        {
+            return p.generic_string();
+        }
+        static const path_string_t::value_type* glob_chars = "[]?*";
+        #endif
+
+        void include_search_glob(std::set<include_search_return> & result,
+            fs::path dir, fs::path path, quickbook::state const & state)
+        {
+            // Split the glob into the current dir/glob/rest to search.
+            fs::path glob;
+            fs::path rest;
+            fs::path::iterator i = path.begin();
+            fs::path::iterator e = path.end();
+            for (; i != e; ++i)
+            {
+                if (path_to_string(*i).find_first_of(glob_chars) != path_string_t::npos)
+                {
+                    glob = *i;
+                    for (++i; i != e; ++i) rest /= *i;
+                    break;
+                }
+                else
+                {
+                    dir /= *i;
+                }
+            }
+            // Walk through the dir for matches.
+            fs::directory_iterator dir_i(dir.empty() ? fs::path(".") : dir);
+            fs::directory_iterator dir_e;
+            for (; dir_i != dir_e; ++dir_i)
+            {
+                fs::path f = dir_i->path().filename();
+                // Skip if the dir item doesn't match.
+                if (!quickbook::glob(path_to_string(glob).c_str(),path_to_string(f).c_str())) continue;
+                // If it's a file we add it to the results.
+                if (fs::is_regular_file(dir_i->status()))
+                {
+                    result.insert(include_search_return(
+                        dir/f,
+                        state.filename_relative.parent_path()/dir/f
+                        ));
+                }
+                // If it's a matching dir, we recurse looking for more files.
+                else
+                {
+                    include_search_glob(result,dir,f/rest,state);
+                }
+            }
+        }
+
         std::set<include_search_return> include_search(path_details const& details,
                 quickbook::state& state, string_iterator pos)
         {
             std::set<include_search_return> result;
 
-            fs::path path = detail::generic_to_path(details.value);
-
-            // If the path is relative, try and resolve it.
-            if (!path.has_root_directory() && !path.has_root_name())
+            // If the path has some glob match characters
+            // we do a discovery of all the matches..
+            if (details.type == path_details::glob)
             {
-                fs::path local_path =
-                    state.current_file->path.parent_path() / path;
+                fs::path current = state.current_file->path.parent_path();
+                fs::path path(details.value);
 
-                // See if it can be found locally first.
-                if (state.add_dependency(local_path))
+                // Search for the current dir accumulating to the result.
+                include_search_glob(result,current,path,state);
+                // Search the include path dirs accumulating to the result.
+                BOOST_FOREACH(fs::path dir, include_path)
                 {
-                    result.insert(include_search_return(
-                        local_path,
-                        state.filename_relative.parent_path() / path));
-                    return result;
+                    include_search_glob(result,dir,path,state);
                 }
+                // Done.
+                return result;
+            }
+            else
+            {
+                fs::path path = detail::generic_to_path(details.value);
 
-                BOOST_FOREACH(fs::path full, include_path)
+                // If the path is relative, try and resolve it.
+                if (!path.has_root_directory() && !path.has_root_name())
                 {
-                    full /= path;
+                    fs::path local_path =
+                       state.current_file->path.parent_path() / path;
 
-                    if (state.add_dependency(full))
+                    // See if it can be found locally first.
+                    if (state.add_dependency(local_path))
                     {
-                        result.insert(include_search_return(full, path));
+                        result.insert(include_search_return(
+                            local_path,
+                            state.filename_relative.parent_path() / path));
                         return result;
                     }
+
+                    // Search in each of the include path locations.
+                    BOOST_FOREACH(fs::path full, include_path)
+                    {
+                        full /= path;
+
+                        if (state.add_dependency(full))
+                        {
+                            result.insert(include_search_return(full, path));
+                            return result;
+                        }
+                    }
                 }
-            }
-            else
-            {
-                if (state.add_dependency(path)) {
-                    result.insert(include_search_return(path, path));
-                    return result;
+                else
+                {
+                    if (state.add_dependency(path)) {
+                        result.insert(include_search_return(path, path));
+                        return result;
+                    }
                 }
-            }
 
-            detail::outerr(state.current_file, pos)
-                << "Unable to find file: "
-                << details.value
-                << std::endl;
-            ++state.error_count;
+                detail::outerr(state.current_file, pos)
+                    << "Unable to find file: "
+                    << details.value
+                    << std::endl;
+                ++state.error_count;
 
-            return result;
+                return result;
+            }
         }
     }
     
diff --git a/tools/quickbook/src/glob.hpp b/tools/quickbook/src/glob.hpp
new file mode 100644
index 0000000..1cb4132
--- /dev/null
+++ b/tools/quickbook/src/glob.hpp
@@ -0,0 +1,227 @@
+#ifndef BOOST_QUICKBOOK_GLOB_HPP
+#define BOOST_QUICKBOOK_GLOB_HPP
+/*
+ Copyright Redshift Software Inc 2011
+ Distributed under the Boost Software License, Version 1.0.
+ (See accompanying file LICENSE_1_0.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+ */
+
+/*
+ * Copyright 1994 Christopher Seiwald.  All rights reserved.
+ *
+ * This file is part of Jam - see jam.c for Copyright information.
+ */
+
+/*
+ * glob.c - match a string against a simple pattern
+ *
+ * Understands the following patterns:
+ *
+ *  *   any number of characters
+ *  ?   any single character
+ *  [a-z]   any single character in the range a-z
+ *  [^a-z]  any single character not in the range a-z
+ *  \x  match x
+ *
+ * External functions:
+ *
+ *  glob() - match a string against a simple pattern
+ *
+ * Internal functions:
+ *
+ *  globchars() - build a bitlist to check for character group match
+ */
+
+#include <boost/cstdint.hpp>
+
+namespace quickbook
+{
+    namespace glob_detail
+    {
+        template < typename Char >
+        struct bit_list
+        {
+            /* bytes used for [chars] in compiled expr */
+            enum bit_list_size_t
+            {
+                bit_list_size = sizeof(Char)/8
+            };
+
+            boost::uint8_t tab[bit_list_size];
+
+            bit_list()
+            {
+                for (unsigned i = 0; i<bit_list_size; ++i)
+                    tab[i] = 0;
+            }
+
+            bool operator[](unsigned bit)
+            {
+                return (tab[bit/8]&(1<<(bit%8)));
+            }
+
+            void set(unsigned bit)
+            {
+                /* `bit != 0` :: Do not include \0 in either $[chars] or $[^chars]. */
+                if (bit!=0)
+                    tab[bit/8] |= (1<<(bit%8) );
+            }
+
+            void negate()
+            {
+                for (unsigned i = 0; i<bit_list_size; ++i)
+                    tab[i] ^= 255;
+            }
+        };
+
+        /*
+         * globchars() - build a bitlist to check for character group match.
+         */
+        template < typename Char >
+        bit_list<Char> globchars(const Char * s, const Char * e)
+        {
+            bit_list<Char> result;
+
+            bool neg = false;
+
+            if (*s==Char('^'))
+            {
+                neg = true;
+                ++s;
+            }
+
+            while (s<e)
+            {
+                Char c;
+
+                if ((s+2<e)&&(s[1]==Char('-')))
+                {
+                    for (c = s[0]; c<=s[2]; ++c)
+                        result.set(c);
+                    s += 3;
+                }
+                else
+                {
+                    c = *s++;
+                    result.set(c);
+                }
+            }
+
+            if (neg)
+                result.negate();
+
+            return result;
+        }
+
+        /*
+         * glob() - match a string against a simple pattern.
+         */
+        template < typename Char >
+        bool glob(const Char * c, const Char * s, bool & fail)
+        {
+            const Char eos = Char('\0');
+
+            fail = false;
+
+            while (true)
+            {
+                if (eos==*c)
+                {
+                    fail = eos!=*s;
+                    return !fail;
+                }
+                else if (Char('?')==*c)
+                {
+                    ++c;
+                    if (eos==*s++)
+                        return false;
+                }
+                else if (Char('[')==*c)
+                {
+                    ++c;
+                    /* Scan for matching ]. */
+
+                    const Char * here = c;
+                    do
+                    {
+                        if (eos==*c++)
+                            return false;
+                    }
+                    while ((here==c)||(*c!=Char(']')));
+                    ++c;
+
+                    /* Build character class bitlist. */
+
+                    glob_detail::bit_list<Char> bitlist =
+                        glob_detail::globchars(here, c);
+
+                    if (!bitlist[*s])
+                        return false;
+                    ++s;
+                }
+                else if (Char('*')==*c)
+                {
+                    ++c;
+                    const Char * here = s;
+
+                    while (eos!=*s)
+                        ++s;
+
+                    /* Try to match the rest of the pattern in a recursive */
+                    /* call.  If the match fails we'll back up chars, retrying. */
+
+                    while (s!=here)
+                    {
+                        bool r = false;
+
+                        /* A fast path for the last token in a pattern. */
+                        if (eos!=*c)
+                            r = glob(c, s, fail);
+                        else if (eos!=*s)
+                        {
+                            fail = true;
+                            r = false;
+                        }
+                        else
+                            r = true;
+
+                        if (r)
+                            return true;
+                        if (fail)
+                            return false;
+                        --s;
+                    }
+                }
+                else if (Char('\\')==*c)
+                {
+                    ++c;
+                    /* Force literal match of next char. */
+                    if (eos==*c||(*s++!=*c++))
+                        return false;
+                }
+                else
+                {
+                    ++c;
+                    if (*s++!=c[-1])
+                        return false;
+                }
+            }
+
+            return false;
+        }
+    }
+
+    /*
+     * glob() - match a string against a simple pattern.
+     */
+    template < typename Char >
+    bool glob(const Char * pattern, const Char * s)
+    {
+        bool fail = false;
+        bool result = glob_detail::glob(pattern, s, fail);
+        return result;
+    }
+}
+
+#endif
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.