Just noticed I forgot to strip some of the references to 'max-levels' out of the documentation updates in patch.subdirs
Here's an updated patch...
diff -Naur aptitude.orig/doc/en/aptitude.xml aptitude.subdirs/doc/en/aptitude.xml --- aptitude.orig/doc/en/aptitude.xml 2007-12-19 12:53:15.000000000 -0500 +++ aptitude.subdirs/doc/en/aptitude.xml 2007-12-20 10:34:45.000000000 -0500 @@ -5357,7 +5357,8 @@ <para> Group based on the whole Section field, so categories like <quote>non-free/games</quote> will be - created. + created. This is the default if no + <replaceable>mode</replaceable> is specified. </para> </listitem> </varlistentry> @@ -5367,10 +5368,11 @@ <listitem> <para> - Group based on the part of the Section field - before the <quote><literal>/</literal></quote>; if there is - no <literal>/</literal>, - <literal>main</literal> will be used instead. + Group based on the part of the Section field before the + first <literal>/</literal> character; if there is no + <literal>/</literal>, + <quote><literal>main</literal></quote> will be used + instead. </para> </listitem> </varlistentry> @@ -5380,10 +5382,23 @@ <listitem> <para> - Group based on the part of the Section field - after the <quote><literal>/</literal></quote>; if there is - no <literal>/</literal>, the entire field will - be used. + Group based on the part of the Section field after the + first <literal>/</literal> character; if there is no + <literal>/</literal>, the entire field will be used. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>subdirs</literal></term> + + <listitem> + <para> + Group based on the part of the Section field after the + first <literal>/</literal> character; if there is no + <literal>/</literal>, the entire field will be used; if + there are multiple <literal>/</literal> characters, + a hierarchy of groups will be formed. </para> </listitem> </varlistentry> diff -Naur aptitude.orig/src/load_grouppolicy.cc aptitude.subdirs/src/load_grouppolicy.cc --- aptitude.orig/src/load_grouppolicy.cc 2007-12-19 12:53:19.000000000 -0500 +++ aptitude.subdirs/src/load_grouppolicy.cc 2007-12-20 10:33:31.000000000 -0500 @@ -364,8 +364,10 @@ split_mode=pkg_grouppolicy_section_factory::split_topdir; else if(!strcasecmp(args[0].c_str(), "subdir")) split_mode=pkg_grouppolicy_section_factory::split_subdir; + else if(!strcasecmp(args[0].c_str(), "subdirs")) + split_mode=pkg_grouppolicy_section_factory::split_subdirs; else - throw GroupParseException(_("Bad section name '%s' (use 'none', 'topdir', or 'subdir')"), args[0].c_str()); + throw GroupParseException(_("Bad section name '%s' (use 'none', 'topdir', 'subdir', or 'subdirs')"), args[0].c_str()); } if(args.size() >= 2) diff -Naur aptitude.orig/src/pkg_grouppolicy.cc aptitude.subdirs/src/pkg_grouppolicy.cc --- aptitude.orig/src/pkg_grouppolicy.cc 2007-12-19 12:53:19.000000000 -0500 +++ aptitude.subdirs/src/pkg_grouppolicy.cc 2007-12-20 10:33:31.000000000 -0500 @@ -213,81 +213,95 @@ void pkg_grouppolicy_section::add_package(const pkgCache::PkgIterator &pkg, pkg_subtree *root) { - const char *section=pkg.VersionList().Section(); - const char *name=pkg.Name(); - bool maypassthrough=false; // FIXME: HACK! - - if(name[0] == 't' && name[1] == 'a' && name[2] == 's' && name[3] == 'k' && name[4] == '-') + // Determine Section + string section; + if(!strncmp(pkg.Name(), "task-", 5)) + section=_("Tasks"); + else if(pkg.VersionList().end()) + section=_("virtual"); + else if(!pkg.VersionList().Section()) + section=_("Unknown"); + else { - maypassthrough=true; - section=split_mode!=pkg_grouppolicy_section_factory::split_none?_("Tasks/Tasks"):_("Tasks"); - } + section=pkg.VersionList().Section(); - if(!section) - { - maypassthrough=true; - section=split_mode!=pkg_grouppolicy_section_factory::split_none?_("Unknown/Unknown"):_("Unknown"); + // Split Section based on split_mode + string::size_type first_split=section.find('/'); + if(split_mode==pkg_grouppolicy_section_factory::split_topdir) + section=(first_split!=section.npos?section.substr(0,first_split):_("main")); + else if((split_mode==pkg_grouppolicy_section_factory::split_subdir || + split_mode==pkg_grouppolicy_section_factory::split_subdirs) && + first_split!=section.npos) + section=section.substr(first_split+1); } - if(pkg.VersionList().end()) + // Pass Through, if necessary + if(passthrough && + ((!section.compare(_("Tasks"))) || + (!section.compare(_("virtual"))) || + (!section.compare(_("Unknown"))))) { - maypassthrough=true; - section=split_mode!=pkg_grouppolicy_section_factory::split_none?_("virtual/virtual"):_("virtual"); + if(sections.find(section)==sections.end()) + sections[section].first=chain->instantiate(get_sig(), get_desc_sig()); + sections[section].first->add_package(pkg, root); } - const char *split=strchr(section, '/'); - const char *subdir=split?split+1:section; - - string tag; - - if(split_mode==pkg_grouppolicy_section_factory::split_none) - tag=section; - else if(split_mode==pkg_grouppolicy_section_factory::split_topdir) - tag=(split?string(section,split-section):string(_("main"))); - else if(split_mode==pkg_grouppolicy_section_factory::split_subdir) - tag=subdir; - - section_map::iterator found=sections.find(tag); - - if(maypassthrough && passthrough) - { - if(found==sections.end()) - sections[tag].first=chain->instantiate(get_sig(), get_desc_sig()); - - sections[tag].first->add_package(pkg, root); - } + // Add Package to Tree else { - if(found==sections.end()) + pkg_subtree *current_root=root; + string section_id; + string sections_remaining=section; + bool done=false; + do { - pkg_subtree *newtree; - string realtag=tag; - - // Go by the last element of the section for multi-level sections. - if(tag.find('/')!=tag.npos) - realtag=string(tag, tag.rfind('/')+1); + if(split_mode==pkg_grouppolicy_section_factory::split_subdirs) + { + string::size_type next_split=sections_remaining.find('/',1); + section_id.append(sections_remaining.substr(0,next_split)); + section=(sections_remaining.at(0)=='/'?sections_remaining.substr(1,next_split):sections_remaining.substr(0,next_split)); + if(next_split==sections_remaining.npos) + done = true; + else + sections_remaining=sections_remaining.substr(next_split); + } + else + { + section_id=section; + done = true; + } - if(section_descriptions.find(realtag)!=section_descriptions.end()) + section_map::iterator found=sections.find(section_id); + if(found==sections.end()) { - wstring desc=section_descriptions[realtag]; + string section_tail=section; + pkg_subtree *newtree; - if(desc.find(L'\n')!=desc.npos) - newtree=new pkg_subtree(cw::util::transcode(tag)+L" - "+wstring(desc, 0, desc.find('\n')), - desc, - get_desc_sig()); + // Go by the last element of the section for multi-level sections. + string::size_type last_split=section.rfind('/'); + if(last_split!=section.npos) + section_tail=section.substr(last_split+1); + + if(section_descriptions.find(section_tail)!=section_descriptions.end()) + { + wstring desc=section_descriptions[section_tail]; + if(desc.find(L'\n')!=desc.npos) + newtree=new pkg_subtree(cw::util::transcode(section)+L" - "+wstring(desc, 0, desc.find('\n')), desc, get_desc_sig()); + else + newtree=new pkg_subtree(cw::util::transcode(section)+desc); + } else - newtree=new pkg_subtree(cw::util::transcode(tag)+desc); - } - else - newtree=new pkg_subtree(cw::util::transcode(tag)); + newtree=new pkg_subtree(cw::util::transcode(section)); - sections[tag].first=chain->instantiate(get_sig(), get_desc_sig()); - sections[tag].second=newtree; + sections[section_id].first=chain->instantiate(get_sig(), get_desc_sig()); + sections[section_id].second=newtree; - root->add_child(newtree); - } + current_root->add_child(newtree); + } + current_root=sections[section_id].second; + } while(!done); - sections[tag].first->add_package(pkg, sections[tag].second); + sections[section_id].first->add_package(pkg, sections[section_id].second); } } diff -Naur aptitude.orig/src/pkg_grouppolicy.h aptitude.subdirs/src/pkg_grouppolicy.h --- aptitude.orig/src/pkg_grouppolicy.h 2007-12-19 12:53:19.000000000 -0500 +++ aptitude.subdirs/src/pkg_grouppolicy.h 2007-12-20 10:33:31.000000000 -0500 @@ -117,12 +117,16 @@ bool passthrough; public: // How to split the 'section' value: - static const int split_none=0; // Don't. This gives you names like "games" and "non-free/editors". + static const int split_none=0; + // Split it and keep the first part (adding an implied "main" to + // packages without a first part). So you get "main", "non-free", etc. static const int split_topdir=1; - // Split it and keep the top-level half (adding an implied "main" to - // packages without a first half). So you get "main", "non-free", etc. + // Split it and keep the second part. So you get "games", "editors", etc. static const int split_subdir=2; + // Split the second part, and, in a single policy, build multiple layers of + // subtrees as needed. + static const int split_subdirs=3; pkg_grouppolicy_section_factory(int _split_mode, bool _passthrough,