Package: aptitude Version: 0.4.10 Severity: wishlist Tags: patch I run my own package repository, and I'd like to be able to organize packages in a Section-based hierarchy that is more than two levels deep.
For example (I'm stealing this example from Daniel Burrows...), imagine a Packages file that says: Package: foo++ Section: devel/c++/template-libraries ... Package: hwidget Section: devel/haskell ... Package: excelsior Section: devel/otherosfs/msoffice/excel/oh/god/the/nesting/horror ... I'd like to see a hierarchy in aptitude that looks like: --\ Not Installed Packages --\ devel --\ c++ --\ template-libraries pp foo++ ... --\ haskell pp hwidget ... --\ otherosfs --\ msoffice --\ excel ... etc. Attached are a few patches to accomplish this... They could probably use some refinement, so feel free to provide feedback, and I'll do my best to make them fit for public consumption. Thanks! -Paul
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-19 15:03:57.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,27 @@ <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, up to + <replaceable>max-levels</replaceable> deep. If + <replaceable>max-levels</replaceable> is set to + <literal>1</literal>, this behaves like + <literal>subdir</literal>. </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-19 15:03:57.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-19 16:04:04.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-19 15:03:57.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,
diff -Naur aptitude.subdirs/src/pkg_grouppolicy.cc aptitude.subdirs.topdir/src/pkg_grouppolicy.cc --- aptitude.subdirs/src/pkg_grouppolicy.cc 2007-12-19 16:04:04.000000000 -0500 +++ aptitude.subdirs.topdir/src/pkg_grouppolicy.cc 2007-12-19 16:12:08.000000000 -0500 @@ -121,6 +121,7 @@ // The descriptions are in the cw::style used by package descriptions. static hash_map<string, wstring> section_descriptions; + static string top_sections; static void init_section_descriptions(); public: pkg_grouppolicy_section(int _split_mode, @@ -151,6 +152,7 @@ } hash_map<string, wstring> pkg_grouppolicy_section::section_descriptions; +string pkg_grouppolicy_section::top_sections; // Should this be externally configurable? void pkg_grouppolicy_section::init_section_descriptions() @@ -200,6 +202,8 @@ section_descriptions["web"]=W_("Web browsers, servers, proxies, and other tools\n Packages in the 'web' section include Web browsers, Web servers and proxies, software to write CGI scripts or Web-based programs, pre-written Web-based programs, and other software related to the World Wide Web."); section_descriptions["x11"]=W_("The X window system and related software\n Packages in the 'x11' section include the core packages for the X window system, window managers, utility programs for X, and miscellaneous programs with an X GUI which were placed here because they didn't fit anywhere else."); + top_sections=_("main,contrib,non-free,non-US"); + section_descriptions["contrib"]=W_("Programs which depend on software not in Debian\n Packages in the 'contrib' section are not part of Debian.\n .\n These packages are Free Software, but they depend on software which is not part of Debian. This may be because it is not Free Software, but is packaged in the non-free section of the archive, because Debian cannot distribute it at all, or (in rare cases) because no-one has packaged it yet.\n .\n For more information about what Debian considers to be Free Software, see http://www.debian.org/social_contract#guidelines"); section_descriptions["main"]=W_("The main Debian archive\n The Debian distribution consists of packages from the 'main' section. Every package in 'main' is Free Software.\n .\n For more information about what Debian considers to be Free Software, see http://www.debian.org/social_contract#guidelines"); section_descriptions["non-US"]=W_("Programs stored outside the US due to export controls\n Packages in 'non-US' likely contain cryptography; a few implement patented algorithms. Because of this, they cannot be exported from the United States, and hence are stored on a server in the \"free world\".\n .\n Note: the Debian Project is currently merging cryptographic software into the US-based archives after consulting with legal experts about recent changes in export policies. Most packages which were formerly found in this section, therefore, are now in 'main'."); @@ -227,11 +231,14 @@ // Split Section based on split_mode string::size_type first_split=section.find('/'); + string first_section=section.substr(0,first_split); + bool is_top_section=(first_split!=section.npos && + top_sections.find(first_section)!=top_sections.npos); if(split_mode==pkg_grouppolicy_section_factory::split_topdir) - section=(first_split!=section.npos?section.substr(0,first_split):_("main")); + section=(is_top_section?first_section:_("main")); else if((split_mode==pkg_grouppolicy_section_factory::split_subdir || split_mode==pkg_grouppolicy_section_factory::split_subdirs) && - first_split!=section.npos) + is_top_section) section=section.substr(first_split+1); }
diff -Naur aptitude.subdirs.topdir/src/pkg_grouppolicy.cc aptitude.subdirs.topdir.cleanup/src/pkg_grouppolicy.cc --- aptitude.subdirs.topdir/src/pkg_grouppolicy.cc 2007-12-19 16:12:08.000000000 -0500 +++ aptitude.subdirs.topdir.cleanup/src/pkg_grouppolicy.cc 2007-12-19 16:18:48.000000000 -0500 @@ -140,7 +140,7 @@ virtual ~pkg_grouppolicy_section() { for(section_map::iterator i=sections.begin(); i!=sections.end(); i++) - delete i->second.first; + delete i->second.first; } }; @@ -164,10 +164,11 @@ section_descriptions["Tasks"]=W_("Packages which set up your computer to perform a particular task\n Packages in the 'Tasks' section contain no files; they merely depend upon other packages. These packages provide an easy way to select a predefined set of packages for a specialized task."); + section_descriptions["Virtual"]=W_("Virtual packages\n These packages do not exist; they are names other packages use to require or provide some functionality."); + section_descriptions["Unknown"]=W_("Packages with no declared section\n No section is given for these packages. Perhaps there is an error in the Packages file?"); section_descriptions["admin"]=W_("Administrative utilities (install software, manage users, etc)\n Packages in the 'admin' section allow you to perform administrative tasks such as installing software, managing users, configuring and monitoring your system, examining network traffic, and so on."); - section_descriptions["alien"]=W_("Packages converted from foreign formats (rpm, tgz, etc)\n Packages in the 'alien' section were created by the 'alien' program from a non-Debian package format such as RPM"); section_descriptions["base"]=W_("The Debian base system\n Packages in the 'base' section are part of the initial system installation."); section_descriptions["comm"]=W_("Programs for faxmodems and other communication devices\n Packages in the 'comm' section are used to control modems and other hardware communications devices. This includes software to control faxmodems (for instance, PPP for dial-up internet connections and programs originally written for that purpose, such as zmodem/kermit), as well as software to control cellular phones, interface with FidoNet, and run a BBS."); @@ -209,8 +210,6 @@ section_descriptions["non-US"]=W_("Programs stored outside the US due to export controls\n Packages in 'non-US' likely contain cryptography; a few implement patented algorithms. Because of this, they cannot be exported from the United States, and hence are stored on a server in the \"free world\".\n .\n Note: the Debian Project is currently merging cryptographic software into the US-based archives after consulting with legal experts about recent changes in export policies. Most packages which were formerly found in this section, therefore, are now in 'main'."); section_descriptions["non-free"]=W_("Programs which are not free software\n Packages in the 'non-free' section are not part of Debian.\n .\n These packages fail to meet one or more of the requirements of the Debian Free Software Guidelines (see below). You should read the license of programs in this section to be sure that you are allowed to use them in the way you intend.\n .\n For more information about what Debian considers to be Free Software, see http://www.debian.org/social_contract#guidelines"); - section_descriptions["virtual"]=W_("Virtual packages\n These packages do not exist; they are names other packages use to require or provide some functionality."); - already_done=true; } @@ -222,7 +221,7 @@ if(!strncmp(pkg.Name(), "task-", 5)) section=_("Tasks"); else if(pkg.VersionList().end()) - section=_("virtual"); + section=_("Virtual"); else if(!pkg.VersionList().Section()) section=_("Unknown"); else @@ -245,7 +244,7 @@ // Pass Through, if necessary if(passthrough && ((!section.compare(_("Tasks"))) || - (!section.compare(_("virtual"))) || + (!section.compare(_("Virtual"))) || (!section.compare(_("Unknown"))))) { if(sections.find(section)==sections.end())
diff -Naur aptitude.orig/doc/en/aptitude.xml aptitude.default/doc/en/aptitude.xml --- aptitude.orig/doc/en/aptitude.xml 2007-12-19 12:53:15.000000000 -0500 +++ aptitude.default/doc/en/aptitude.xml 2007-12-19 13:12:16.000000000 -0500 @@ -9123,7 +9123,7 @@ <seglistitem id='configDefault-Grouping'> <seg><literal>Aptitude::UI::Default-Grouping</literal></seg> - <seg><literal>filter(missing),status,section(subdir,passthrough),section(topdir)</literal></seg> + <seg><literal>filter(missing),status,section(subdirs,passthrough),section(topdir)</literal></seg> <seg> Sets the default grouping policy used for diff -Naur aptitude.orig/src/ui.cc aptitude.default/src/ui.cc --- aptitude.orig/src/ui.cc 2007-12-19 12:53:20.000000000 -0500 +++ aptitude.default/src/ui.cc 2007-12-19 13:13:11.000000000 -0500 @@ -198,7 +198,7 @@ const char *default_pkgstatusdisplay="%d"; const char *default_pkgheaderdisplay="%N %n #%B %u %o"; -const char *default_grpstr="task,status,section(subdir,passthrough),section(topdir)"; +const char *default_grpstr="task,status,section(subdirs,passthrough),section(topdir)"; void ui_start_download(bool hide_preview) { @@ -823,7 +823,7 @@ if(!grp) // Eek! The default grouping failed to parse. Fall all the // way back. - grp=new pkg_grouppolicy_task_factory(new pkg_grouppolicy_status_factory(new pkg_grouppolicy_section_factory(pkg_grouppolicy_section_factory::split_subdir,true,new pkg_grouppolicy_section_factory(pkg_grouppolicy_section_factory::split_topdir,false,new pkg_grouppolicy_end_factory())))); + grp=new pkg_grouppolicy_task_factory(new pkg_grouppolicy_status_factory(new pkg_grouppolicy_section_factory(pkg_grouppolicy_section_factory::split_subdirs,true,new pkg_grouppolicy_section_factory(pkg_grouppolicy_section_factory::split_topdir,false,new pkg_grouppolicy_end_factory())))); } pkg_tree_ref tree=pkg_tree::create(grpstr.c_str(), grp); @@ -851,7 +851,7 @@ progress_ref p = gen_progress_bar(); pkg_grouppolicy_factory *grp = new pkg_grouppolicy_end_factory(); - std::string grpstr="section(subdir, passthrough)"; + std::string grpstr="section(subdirs, passthrough)"; pkg_tree_ref tree=pkg_tree::create(grpstr.c_str(), grp, L"!~v!~i~RBrecommends:~i");