サフィックス・パターン付き basename

path_type に、 basename の引数にサフィックスのパターンを指定して、 サフィックスを除去した名前を返すメンバ関数を追加します。 このパターンには、 * と ? のワイルドカードを使うことができます。 両方共ドット以外の文字にマッチすることにしておきます。

void
test_basename_suffix (test::simple& ts)
{
    path_type path ("/foo/bar/index.html");
    ts.ok (path.basename () == "index.html", "basename index.html");
    ts.ok (path.basename (".html") == "index", "basename(.html) index");
    ts.ok (path.basename (".ht?l") == "index", "basename(.ht?l) index");
    ts.ok (path.basename (".*") == "index", "basename(.*) index");
}

メンバ関数の宣言を追加します。

class path_type {
    std::vector<std::string> mseg;
public:
    // 略
    std::string basename () const;
    std::string basename (std::string const& pattern) const;
    // 略
private:
    bool match_wildcards (std::string const &pattern, std::string const& name,
        std::size_t r, std::size_t t) const;
    std::size_t count_dots (std::string const& s) const;
};

パターン付き basename は、 末尾側のドットを探して、 そこからパターンが一致するかどうかを調べます。 一致したら、 その部分を省いた名前を返します。 一致しなかったときは、 名前を全部返します。

std::string
path_type::basename (std::string const& pattern) const
{
    if (mseg.empty () || count_dots (mseg.back ()) > 0)
        return "";
    std::string name = mseg.back ();
    std::size_t t = name.rfind ('.');
    if (t == 0 || t == std::string::npos)
        return name;
    std::size_t n = match_wildcards (pattern, name, 0, t) ? t : name.size ();
    return name.substr (0, n);
}

ワイルドカードを含むパターンの一致検査メンバ関数は、 欲張りマッチング型のバックトラック正規表現エンジンを元にしています。

bool
path_type::match_wildcards (std::string const &pattern, std::string const& name,
    std::size_t r, std::size_t t) const
{
    for (; r < pattern.size (); ++r, ++t) {
        if (pattern[r] == '*') {
            std::size_t s = t;
            while (s < name.size () && name[s] != '.')
                ++s;
            do {
                if (match_wildcards (pattern, name, r + 1, s))
                    return true;
            } while (s-- > t);
            return false;
        }
        if (t == name.size ())
            return false;
        if (! ((pattern[r] == '?' && name[t] != '.') || pattern[r] == name[t]))
            return false;
    }
    return t == name.size ();
}