/////////////////////////////////////////////////////////////////////////////
// Name:        packageinfo.cpp
// Purpose:     wxPackageInfo
// Author:      Julian Smart, Francesco Montorsi, Daniel J. Lauk
// Modified by:
// Created:     2004-11-07
// RCS-ID:      $Id: packageinfo.cpp,v 1.22 2007/02/01 11:03:51 frm Exp $
// Copyright:   (c) Julian Smart
// Licence:     wxWidgets licence
/////////////////////////////////////////////////////////////////////////////


// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

// includes
#include <wx/fileconf.h>
#include <wx/filename.h>
#include <wx/zipstrm.h>
#include <wx/wfstream.h>
#include <wx/ptr_scpd.h>
#include <wx/dir.h>

#include "wxp/package.h"
#include "wxp/wxp.h"

// static objects
wxPackageHTMLOutputFormatHashMap wxPackageInfo::s_hashmapHTMLFormats;
wxStringHashMap wxPackageInfo::s_hashmapKeywords;

// definitions

wxString wxPackageCategoryName[] =
{
    _("Application"),
    _("Control"),
    _("Dialog"),
    _("Database"),
    _("Data container"),
    _("Graphics"),
    _("Import-export"),
    _("Miscellaneous"),
    _("Networking"),
    _("Printing"),
    _("Stream"),
    _("System"),
    _("Text processing"),
    _("Tutorial-documentation"),
    _("Window container"),
    _("Window layout"),
    _("Wrapper"),
    _("XRC")
};

wxString wxPackageLicenseTypeName[] =
{
    _T("Open Source"),
    _T("Commercial"),
    _T("Shareware"),
    _T("Freeware"),
    _T("Other")
};

wxString wxPackageBuildSystemTypeName[] =
{
    _T("Bakefile"),
    _T("CMake"),
    _T("Scons"),
    _T("Makefile")
};

wxString wxPackageDevelopmentStatusName[] =
{
    _T("Alpha"),
    _T("Beta"),
    _T("Stable")
};

wxString wxPackageProgLanguageName[] =
{
    _T("C++"),
    _T("Lua"),
    _T("Python"),
    _T("Ruby"),
    _T("Perl")
};

wxString wxPackageCompressionModeName[] =
{
    _T("zip"),
    _T("tar.gz")
};

wxString wxPackageExcludeModeName[] =
{
    _("file"),
    _("directory"),
    _("pattern")
};

// RTTI
IMPLEMENT_DYNAMIC_CLASS(wxPackageInfo, wxObject)



// ----------------------------------------------------------------------------
// string2enum helpers
// ----------------------------------------------------------------------------

wxIMPLEMENT_STRING2ENUM(PackageCategory, wxPC_MAX)
wxIMPLEMENT_STRING2ENUM(PackageLicenseType, wxPLT_MAX)
wxIMPLEMENT_STRING2ENUM(PackageDevelopmentStatus, wxPDS_MAX)
wxIMPLEMENT_STRING2ENUM(PackageProgLanguage, wxPPL_MAX)
wxIMPLEMENT_STRING2ENUM(PackageCompressionMode, wxPCM_MAX)
wxIMPLEMENT_STRING2ENUM(PackageExcludeMode, wxPEM_MAX)


// ----------------------------------------------------------------------------
// wxPackageInfo
// ----------------------------------------------------------------------------

wxPackageInfo::wxPackageInfo()
{
    m_nLicenseType = wxPLT_INVALID;
    m_buildSystemType = wxPBST_INVALID;
    m_devStatus = wxPDS_INVALID;
    m_language = wxPPL_INVALID;
    m_catPrimary = wxPC_INVALID;
    m_catSecondary = wxPC_INVALID;
    m_compressMode = wxPCM_INVALID;

    m_nSamples = 0;
    m_nCompressedSize = 0;
    m_nSupportedFormats = 0;
}

bool wxPackageInfo::IsOk() const
{
    if (!wxPackageId::IsOk())
        return false;

    // VERY IMPORTANT: the size and MD5 data can be empty, if the package
    //                 has not been loaded from a WXL file. Thus don't check
    //                 those variables!

    // all data of the BASIC section must be non-empty
    if (m_strDescription.IsEmpty() || m_arrKeywords.GetCount() == 0 ||
        m_strWebsite.IsEmpty())
        return false;
    if (!m_creationDate.IsValid())
        return false;
    if (m_catPrimary == wxPC_INVALID || m_catSecondary == wxPC_INVALID)
        return false;

    // the root of the package should not be empty
    if (m_strRoot.IsEmpty())
        return false;
    if (m_compressMode == wxPCM_INVALID)
        return false;
    if (m_strFileName.IsEmpty())
        return false;

    // also the build system type must be a valid value!
    if (m_buildSystemType == wxPBST_INVALID)
        return false;

    // and we must support at least one format for our build system
    // and for the current platform!
    bool atleastone = false;
    for (size_t i=0; i<wxPCF_MAX; i++)
    {
        if (SupportsBuildSysFormat(wxIdx2PackageCompilerFormat(i)))
        {
            atleastone = true;
            break;
        }
    }
    if (!atleastone)
        return false;

    // NOTE: the following arrays may also be empty:
    //           m_arrDownloadHref, m_arrDocumentation, m_arrScreenshots,
    //           m_arrTestedPort, m_arrTestedVersion, m_arrDependencies;
    //       having them empty does not mean the package is invalid, but
    //       still other GUI parts of the project will give out warnings
    //       (not errors) about them.

    // no supported versions of ports is an error
    if (m_arrSupportedPort.GetCount() == 0 || 
        m_arrSupportedVersion.GetCount() == 0)
        return false;

    for (size_t j=0; j<m_arrSupportedPort.GetCount(); j++)
        if (m_arrSupportedPort[j] == wxPORT_UNKNOWN)
            return false;
    for (size_t j=0; j<m_arrTestedPort.GetCount(); j++)
        if (m_arrTestedPort[j] == wxPORT_UNKNOWN)
            return false;

    // all arrays of non POD objects must be valid, too

    for (size_t j=0; j<m_arrSupportedVersion.GetCount(); j++)
        if (!m_arrSupportedVersion[j].IsOk())
            return false;
    for (size_t j=0; j<m_arrTestedVersion.GetCount(); j++)
        if (!m_arrTestedVersion[j].IsOk())
            return false;

    if (!m_arrDependencies.IsOk())
        return false;
    if (!m_opt.IsOk())
        return false;

    // check command array
    for (size_t i=0; i < wxPBSS_MAX; i++)
    {
        if (!m_arrCommands[i].IsOk())
            return false;

        const wxPackageCommandArray &arr = m_arrCommands[i];
        for (size_t j=0; j < arr.GetCount(); j++)
            if (!arr[j].GetCondition().IsValidForStage(wxIdx2PackageBuildSystemStage(i)))
                return false;
    }

    // a package can't list itself as dependency,
    // regardless of the version of the dependency !
    if (!m_arrDependencies.IsOk() ||
        m_arrDependencies.ContainsDependencyNamed(GetName()))
        return false;

    return true;
}

bool wxPackageInfo::SupportsWxPort(wxPortId p, const wxVersion &ver) const
{
    for (size_t i=0; i<m_arrSupportedPort.GetCount(); i++)
    {
        if (m_arrSupportedPort[i] != p)
            continue;

        if (m_arrSupportedVersion[i].Contains(ver))
            return true;
    }

    return false;
}

void wxPackageInfo::AddBuildSysCommand(long stages, const wxPackageCommand &cmd)
{
    // NB: it's important to preserve all stages supported by each command
    //     even when they are copied in m_arrCommands[i] as wxPackageCommandSet
    //     relies on that to recognize a command which belongs to a commandset

    for (size_t i=0; i<wxPBSS_MAX; i++)
        if (stages & wxIdx2PackageBuildSystemStage(i))
            m_arrCommands[i].Add(cmd);
}

void wxPackageInfo::AddBuildSysCommands(const wxPackageCommandArray &arr)
{
    for (size_t i=0; i<arr.GetCount(); i++)
        AddBuildSysCommand(arr[i].GetCondition().GetStages(), arr[i]);
}

wxPackageCommandArray wxPackageInfo::GetSubstCmdForStage(
                wxPackageBuildSystemStage stage,
                long flags,
                const wxPackageCondition &cond) const
{
    const wxPackageCommandArray &original =
        m_arrCommands[wxPackageBuildSystemStage2Idx(stage)];
    
    wxPackageCommandArray ret;
    for (size_t i=0; i < original.GetCount(); i++)
    {
        const wxPackageCommand &cmd = original.GetSubstitutedCmd(i);

        // the GetCmd() internal utility may be overridden:
        ret.Add(GetCmd(cmd, stage, flags, cond));
    }

    // copy available targets and selected one:
    ret.CopyTargets(original);

    return ret;
}

wxPackageCommandOptionArray wxPackageInfo::GetSubstBuildSysOptions() const
{
    const wxPackageCommandOptionArray &original = m_opt;
    
    wxPackageCommandOptionArray ret;
    for (size_t i=0; i < original.GetCount(); i++)
    {
        wxPackageCommandOption opt = original[i];

        // VERY IMPORTANT: do not touch the default-value, specially if it
        //                 contains $(keys) which could be substituted!
        // If we touched the default value in fact, then when changing the
        // selected compiler format in the build dialog, the command option
        // values could not be updated correctly!
        opt.SetValue(SubstituteInfo(opt.GetValue()));

        ret.Add(opt);
    }

    // copy the description:
    ret.SetDescription(original.GetDescription());

    return ret;
}

wxPackageCommand wxPackageInfo::GetCmd(const wxPackageCommand &cmd, 
                                       wxPackageBuildSystemStage stage,
                                       long flags,
                                       const wxPackageCondition &cond) const
{
    // get command
    wxPackageCommand ret(cmd);
    wxString subst(ret.GetCommand());

    // build the hashmap for the substitutions
    // FIXME: maybe this should be cached
    wxStringHashMap hash;

    // do $(*option*) substitutions
    wxStringHashMap opthash = m_opt.GetSubstitutionOptionsHashMap(flags, cond);
    wxMergeHashMap(hash, opthash);

    // do also the special $(stageoptions) substitution, getting its
    // value from the hashmap itself:
    wxString stagename = wxPackageBuildSystemStage2String(stage).Lower();
    wxString value = hash[stagename + wxT("options")];

    // note that value maybe the empty string but nonetheless, if 
    // GetSubstitutionOptionsHashMap() worked correctly, the key should be in the
    // hashmap!
    wxASSERT_MSG(hash.find(stagename + wxT("options")) != hash.end(), 
                 wxT("The selected stage should have a key in the hashmap;")
                 wxT("at the very least with an empty value!"));

    hash[wxT("stageoptions")] = value;

    ret.SetCommand(wxDoStringSubstitution(subst, hash));
    return ret;
}

bool wxPackageInfo::SupportsBuildSysFormat(wxPackageCompilerFormat fmt) const
{
    if (!wxIsCompilerFormatSupportedBy(fmt, GetBuildSystemType()))
        return false;

    if (!wxIsCompilerFormatSupportedByThisPlatform(fmt))
        return false;

    return (m_nSupportedFormats & fmt) != 0;
}

wxArrayString wxPackageInfo::GetUsedOptionPresets() const
{
    wxArrayString arr;

    wxPackageCommandOptionArrayHashMap::iterator it;
    for( it = wxPresetXMLDescriptor::s_hashmapOptions.begin();
         it != wxPresetXMLDescriptor::s_hashmapOptions.end(); ++it )
    {
        wxString name = it->first;
        wxPackageCommandOptionArray &value = it->second;

        if (m_opt.Contains(value))
            arr.Add(name);
    }

    return arr;
}

wxString wxPackageInfo::GetUsedCommandPreset(wxStringHashMap &h) const
{
    wxPackageCommandSetHashMap::const_iterator it;
    for( it = wxPresetXMLDescriptor::s_hashmapCommands.begin();
         it != wxPresetXMLDescriptor::s_hashmapCommands.end(); ++it )
    {
        wxString name = it->first;
        const wxPackageCommandSet &set = it->second;

        // multiple command presets for the same package are not supported!
        if (set.IsUsedBy(m_arrCommands, h))
            return name;
    }

    return wxEmptyString;
}

/* static */
bool wxPackageInfo::LoadStandardOutputFormats(const wxString &path)
{
    wxDir dir(path);
    if ( !dir.IsOpened() )
        return false;

    // each directory under given path is considered to contain an
    // 'output format': i.e. two files "details.html" and "summary.html"
    // which allow to format wxPackageInfo data.
    wxString dirname;
    bool cont = dir.GetFirst(&dirname, wxEmptyString, wxDIR_DIRS);
    while ( cont )
    {
        wxPackageHTMLOutputFormat fmt;
        if (fmt.Load(path + wxFileName::GetPathSeparator() + dirname))
            s_hashmapHTMLFormats[dirname] = fmt;

        cont = dir.GetNext(&dirname);
    }

    return !s_hashmapHTMLFormats.empty();
}

wxString wxPackageInfo::GetProjectFolder(const wxString &wxppath) const
{
    wxFileName fn(wxppath);
    if (!fn.IsOk() || !fn.IsAbsolute())
        return wxEmptyString;     // invalid filename!

    // transform the relative root of the package
    const wxChar sep = wxFileName::GetPathSeparator();
    wxString relroot = GetRelativeRoot();
    relroot.Replace(wxT("/"), wxString(sep, 1));

    if (!relroot.StartsWith(wxT("..")))
        return wxEmptyString;     // invalid relative root!

    relroot.Remove(0, 2);
    if (relroot[0] == sep)
        relroot.Remove(0, 1);

    fn = wxFileName::DirName(fn.GetPath(wxPATH_GET_SEPARATOR) + relroot);
    fn.Normalize();

    // returns an absolute path
    return fn.GetPath();
}

wxString wxPackageInfo::GetDownloadLink() const
{
    if (!m_strDownloadFromRepoHref.IsEmpty())
        return SubstituteInfo(m_strDownloadFromRepoHref);

    size_t n = m_arrDownloadHref.GetCount();
    if (n == 0)
        return wxEmptyString;

    // choose a random mirror:
    double idx = ((double)rand()) / RAND_MAX * n; 
    size_t realIdx = wxClip((size_t)idx, 0, n);

    return SubstituteInfo(m_arrDownloadHref[realIdx]);
}



// ----------------------------------------------------------------------------
// wxPackageInfo::SubstituteInfo and global utilities for formatting arrays
// ----------------------------------------------------------------------------

wxString FormatStringArrayWithHref(const wxArrayString &arr, const wxArrayString &href)
{
    wxString ret = wxT("<ul>");
    for (size_t i=0; i < arr.GetCount(); i++)
    {
        if (href[i] != wxEmptyString)
            ret += wxString::Format(wxT("<li><a href='%s'>%s</a></li>"),
                                        href[i].c_str(), arr[i].c_str());
        else
            ret += wxT("<li>") + arr[i] + wxT("</li>");
    }

    ret += wxT("</ul>");
    return ret;
}

wxString FormatStringArrayWithHref(const wxPackageDataWithRefArray &arr,
                                   wxPackageSubstituteInfoContext ctx)
{
    wxString ret = wxT("<ul>");
    for (size_t i=0; i < arr.GetCount(); i++)
    {
        wxString ref;
        switch (ctx)
        {
            case wxPSIC_WXHTML:
                // we can safely use the "best" reference since our output will
                // be (presumably) fetched to a wxHtmlWindow
                ref = arr[i].GetBestRef();
                break;

            case wxPSIC_XHTML:
                // we _must_ use the href because the "best" reference may include
                // protocols such as "wxz:" or anchors like "#zip:" which are not XHTML-valid
                ref = arr[i].GetHref();
                break;

            default:
                wxASSERT(0);
        }

        if (ref != wxEmptyString)
        {
            wxString text = arr[i].GetData();
            if (text.IsEmpty())
                text = ref;

            if (!text.IsEmpty())
                ret += wxString::Format(wxT("<li><a href='%s'>%s</a></li>"),
                                        ref.c_str(), text.c_str());
        }
        else if (!arr[i].GetData().IsEmpty())
            ret += wxT("<li>") + arr[i].GetData() + wxT("</li>");
        //else: skip this reference as it's invalid!
    }

    ret += wxT("</ul>");
    return ret;
}

wxString FormatPortListWithVersions(bool all_same, const wxArrayInt &ports,
                                    const wxVersionRangeArray &versions, wxString &htmlstr)
{
    if (all_same)
    {
        // don't write the list of _all_ supported/tested ports...
        wxString ret = wxT("all ports, version(s) ") + versions[0].GetAsString();
        htmlstr = wxT("<ul><li>") + ret + wxT("</li></ul>");
        return ret;
    }

    wxString ret;
    htmlstr = wxT("<ul>");
    for (size_t i=0; i < ports.GetCount(); i++)
    {
        wxPortId port = (wxPortId)ports[i];
        wxString str = wxPlatformInfo::GetPortIdName(port, false) +
                            wxT(" ") + versions[i].GetAsString();

        ret += str + wxT(", ");
        htmlstr += wxT("<li>") + str + wxT("</li>");
    }

    htmlstr += wxT("</ul>");
    if (ret.IsEmpty())
        ret = wxT("None");
    else
        ret = ret.Left(ret.Len()-2);

    return ret;
}

wxString StringOrNone(const wxString &str)
{
    if (str.IsEmpty() || str == wxT("<ul></ul>"))
        return wxT("None");
    return str;
}

wxString wxPackageInfo::SubstituteInfo(const wxString &str, wxPackageSubstituteInfoContext ctx) const
{
    wxASSERT(ctx > wxPSIC_INVALID && ctx < wxPSIC_MAX);

    if (!IsCachedSubstitutionHashMapValid(ctx))
        wx_const_cast(wxPackageInfo*, this)->UpdateSubstitutionHashMap(ctx);

    // hopefully the substitution hashmap is up2date:
    // if it isn't it's a bug because somewhere the InvalidateCachedSubstHashMap()
    // has not been called
    return wxDoStringSubstitution(str, m_hashmapSubstitution[ctx]);
}

void wxPackageInfo::InvalidateCachedSubstHashMap(wxPackageSubstituteInfoContext ctx)
{ 
    if (ctx == wxPSIC_INVALID)
    {
        // invalidate all hashmaps:
        for (size_t i=0; i<wxPSIC_MAX; i++)
            m_hashmapSubstitution[i].clear();
    }
    else
        m_hashmapSubstitution[ctx].clear();
}

void wxPackageInfo::UpdateSubstitutionHashMap(wxPackageSubstituteInfoContext ctx)
{
    if (ctx == wxPSIC_INVALID)
    {
        for (size_t i=0; i<wxPSIC_MAX; i++)
        {
            wxPackageSubstituteInfoContext ctx = (wxPackageSubstituteInfoContext)i;

            DoUpdateSubstitutionHashMap(ctx);
            wxNormalizeSubstitutionHashMap(m_hashmapSubstitution[ctx]);
        }
    }
    else
    {
        DoUpdateSubstitutionHashMap(ctx);
        wxNormalizeSubstitutionHashMap(m_hashmapSubstitution[ctx]);
    }
}

void wxPackageInfo::DoUpdateSubstitutionHashMap(wxPackageSubstituteInfoContext ctx)
{
    wxASSERT(ctx > wxPSIC_INVALID && ctx < wxPSIC_MAX);


    // 1) create the strings with the data to substitute

    // format some HTML helpers
    wxString authors_html = FormatStringArrayWithHref(GetAuthors(), GetAuthorsHref());
    wxString maintainers_html = FormatStringArrayWithHref(GetMaintainers(), GetMaintainersHref());
    wxString download_html = FormatStringArrayWithHref(GetDownloadHref(), GetDownloadHref());
    wxString docs_html = FormatStringArrayWithHref(GetDocumentation(), ctx);
    wxString screenshots_html = FormatStringArrayWithHref(GetScreenshots(), ctx);

    // truncate the package description if too long
    wxString truncdesc = GetDescription();
    if (truncdesc.Len() > 255)
        truncdesc = truncdesc.Truncate(255) +
                        wxString::Format(wxT("... [more info in the <a href='%s'>")
                                         wxT("project website</a>]"), GetWebsite().c_str());

    // build the supported/tested port lists
    bool allsame_supported_ports =
        GetSupportedPortFlag() == wxGetFullPortIdList() && HasSameSupportedPortVersions();
    bool allsame_tested_ports =
        GetTestedPortFlag() == wxGetFullPortIdList() && HasSameTestedPortVersions();

    wxString wxsupported_html, wxtested_html;

    wxString wxsupported = FormatPortListWithVersions(allsame_supported_ports,
                                GetSupportedPorts(), GetSupportedVersions(), wxsupported_html);
    wxString wxtested = FormatPortListWithVersions(allsame_tested_ports,
                                GetTestedPorts(), GetTestedVersions(), wxtested_html);

    // build the external dependencies list (both as HTML list and plain list)
    wxString extdep, extdep_html;
    size_t max = GetDependencies().GetCount();
    extdep_html = wxT("<ul>");
    for (size_t j=0; j < max; j++)
    {
        wxPackageDependency dep(GetDependencies().Item(j));
        extdep_html += wxString::Format(wxT("<li><b><a href='%s'>%s</a></b> %s</li>"),
                                        dep.GetResolutionHref().c_str(),
                                        dep.GetName().c_str(),
                                        dep.GetVersion().GetAsString().c_str());
        extdep += dep.GetName().c_str() + dep.GetVersion().GetAsString() + wxT(", ");
    }

    extdep_html += wxT("</ul>");
    if (extdep.EndsWith(wxT(", ")))
        extdep = extdep.Left(extdep.Len()-2);

    // logo keyword:
    wxString logo;
    switch (ctx)
    {
        case wxPSIC_WXHTML:
            // we can safely use the "best" reference since our output will
            // be (presumably) fetched to a wxHtmlWindow
            logo = GetLogoRef().GetBestRef();

            // if the logo for this package is missing, use a default one
            if (logo.IsEmpty())
                logo = wxT("memory:wxlogo.png");
            break;

        case wxPSIC_XHTML:
            // we _must_ use the href because the "best" reference may include
            // protocols such as "wxz:" or anchors like "#zip:" which are not XHTML-valid
            logo = GetLogoRef().GetHref();

            // if the logo for this package is missing, use a default one
            if (logo.IsEmpty())
                logo = wxT("missing-logo.png");
            break;

        default:
            wxASSERT(0);
    }

    // build system option list
    wxString opts;
    max = GetBuildSysOptions().GetCount();
    for (size_t j=0; j < max; j++)
        opts += GetBuildSysOptions().Item(j).GetName() + wxT(", ");
    if (opts.EndsWith(wxT(", ")))
        opts = opts.Left(opts.Len()-2);


    // 2) add the data to substitute to the hashmap

    // clean the hashmap
    wxStringHashMap &keywords = m_hashmapSubstitution[ctx];
    keywords.clear();

#define ADD_KEYWORD(name, value)   \
    keywords[wxT(name)] = value;

    // BASIC data
    ADD_KEYWORD("package", GetName());      // $(package) and $(name) are synonims
    ADD_KEYWORD("name", GetName());
    ADD_KEYWORD("version", GetVersion());
    ADD_KEYWORD("id", GetName() + wxT(" ") + GetVersion());
    ADD_KEYWORD("primarycat", GetPrimaryCategoryStr());
    ADD_KEYWORD("secondarycat", GetSecondaryCategoryStr());
    ADD_KEYWORD("keywords", StringOrNone(GetKeywordsStr()));
    ADD_KEYWORD("description", wxStripOutHTML(GetDescription()));
    ADD_KEYWORD("website", GetWebsite());
    ADD_KEYWORD("creation-date", GetCreationDate().FormatDate());

    // PACKAGING data
    ADD_KEYWORD("root", GetRelativeRoot());
    ADD_KEYWORD("compression", GetCompressionModeStr());
    ADD_KEYWORD("filename", GetOutputFileName());
    ADD_KEYWORD("upload", GetUploadDestination());

    // LICENSING data
    ADD_KEYWORD("license", GetLicenseRef().GetData());
    ADD_KEYWORD("licensetype", GetLicenseTypeStr());
    ADD_KEYWORD("organization", GetOrganization());
    ADD_KEYWORD("organization-href", GetOrganizationHref());
    ADD_KEYWORD("authors", StringOrNone(wxArray2String(GetAuthors(), wxT(", "))));
    ADD_KEYWORD("maintainers", StringOrNone(wxArray2String(GetMaintainers(), wxT(", "))));
    ADD_KEYWORD("cost", StringOrNone(GetCost()));
    ADD_KEYWORD("credits", GetCredits());
    ADD_KEYWORD("copyright", GetCopyright());

    // DETAILS data
    ADD_KEYWORD("status", GetDevelopmentStatusStr());
    ADD_KEYWORD("proglanguage", GetProgLanguageStr());
    ADD_KEYWORD("rss", StringOrNone(GetRSSHref()));
    ADD_KEYWORD("logo", logo);
    ADD_KEYWORD("numsamples", wxString::Format(wxT("%d"), GetNumberOfSamples()));

    // DEPENDENCIES data
    ADD_KEYWORD("wxsupported", wxsupported);
    ADD_KEYWORD("wxtested", wxtested);
    ADD_KEYWORD("dependencies", StringOrNone(extdep));

    // BUILD SYSTEM data
    ADD_KEYWORD("buildsystemtype", GetBuildSystemTypeStr());
    ADD_KEYWORD("buildoptions", opts);


    // HTML-specific keywords:

    // BASIC keywords

    ADD_KEYWORD("description-html", GetDescription());
    ADD_KEYWORD("trunc-description-html", truncdesc);

    // LICENSING keywords

    ADD_KEYWORD("authors-html", StringOrNone(authors_html));
    ADD_KEYWORD("maintainers-html", StringOrNone(maintainers_html));
    ADD_KEYWORD("wxsupported-html", StringOrNone(wxsupported_html));
    ADD_KEYWORD("wxtested-html", StringOrNone(wxtested_html));

    // DETAILS keywords

    ADD_KEYWORD("downloads-html", StringOrNone(download_html));
    ADD_KEYWORD("documentation-html", StringOrNone(docs_html));
    ADD_KEYWORD("screenshots-html", StringOrNone(screenshots_html));
    ADD_KEYWORD("dependencies-html", StringOrNone(extdep_html));


    // add to the hashmap all the standard substitutions:
    wxMergeHashMap(keywords, GetStandardSubstitutionHashMap());
}

/* static */
wxStringHashMap wxPackageInfo::GetStandardSubstitutionHashMap()
{
    // copy the s_hashmapKeywords hashmap
    wxStringHashMap ret(s_hashmapKeywords);

    // add some standard things:
    ret[wxT("dollar")] = wxT("$");
    ret[wxT("dirsep")] = wxString(wxFileName::GetPathSeparator());

    return ret;
}

