Module: release-info-internals Synopsis: Functional Developer release information Author: Andy Armstrong Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND // Useful constants define constant $library-pack-extension = "dlp"; /// Release info define class () end class ; define generic info-name (info :: ) => (name :: false-or()); define generic info-title (info :: ) => (title :: ); define generic info-description (info :: ) => (description :: false-or()); define method info-description (info :: ) => (title :: ) "" end method info-description; /// Named release info define class () constant slot info-name :: , required-init-keyword: name:; end class ; define method info-title (info :: ) => (title :: ) as-lowercase(as(, info.info-name)) end method info-title; /// Described release info define class () constant slot info-title :: = "", init-keyword: title:; constant slot info-description :: = "", init-keyword: description:; end class ; /// Libraries define constant *libraries* :: = make(); define class () slot info-library-pack :: false-or() = #f, init-keyword: library-pack:; constant slot info-categories :: = #[], init-keyword: categories:; constant slot info-project :: false-or() = #f, init-keyword: project:; constant slot info-modules :: = #[], init-keyword: modules:; constant slot info-requires :: = #[], init-keyword: requires:; constant slot info-source-directory :: false-or() = #f, init-keyword: source-directory:; slot info-binary :: false-or() = #f, init-keyword: binary:; slot info-binary-name :: false-or() = #f, init-keyword: binary-name:; slot info-merge-parent :: false-or() = #f, init-keyword: merge-parent:; slot info-database :: false-or() = #f, init-keyword: database:; slot info-lib :: false-or() = #f, init-keyword: lib:; slot info-releases :: = #[], init-keyword: releases:; end class ; define method initialize (info :: , #key) => () next-method(); *libraries*[info.info-name] := info end method initialize; define class () constant slot info-binary-name :: , required-init-keyword: binary-name:; constant slot info-merged-libraries :: = #[], init-keyword: merged-libraries:; end class ; define class () constant slot info-product :: , required-init-keyword: product:; constant slot info-version :: , required-init-keyword: version:; constant slot info-platform :: , required-init-keyword: platform:; constant slot info-relative-location :: , required-init-keyword: relative-location:; constant slot info-binary :: false-or() = #f, init-keyword: binary:; constant slot info-database :: , required-init-keyword: database:; constant slot info-lib :: , required-init-keyword: lib:; end class ; define method find-library-info (name :: ) => (library :: false-or()) find-library-info(as(, name)) end method find-library-info; define method find-library-info (name :: ) => (library :: false-or()) element(*libraries*, name, default: #f) end method find-library-info; define method info-location (info :: ) => (location :: ) /*---*** This is ultimately the right implementation merge-locators(make(, name: info.info-project, directory: info.info-source-directory), info.info-library-pack.info-location) */ merge-locators(make(, name: info.info-project, directory: info.info-source-directory), release-sources-directory()) end method info-location; define method info-location (info :: ) => (location :: ) merge-locators(make(, name: info.info-project, directory: info.info-source-directory), release-examples-directory()) end method info-location; define method info-location (info :: ) => (location :: ) merge-locators(make(, name: info.info-project, directory: info.info-source-directory), release-sources-directory()) end method info-location; /// Library Packs define constant *library-packs* :: = make(); define class () constant slot info-manual :: false-or() = #f, init-keyword: manual:; constant slot info-pack-number :: false-or() = #f, init-keyword: number:; constant slot info-required? :: = #f, init-keyword: required?:; constant slot info-author :: false-or() = #f, init-keyword: author:; constant slot info-company :: false-or() = #f, init-keyword: company:; constant slot info-copyright :: false-or() = #f, init-keyword: copyright:; end class ; define method initialize (info :: , #key) => () next-method(); *library-packs*[info.info-name] := info; if (info.info-pack-number) install-numbered-library-pack-info(info) end end method initialize; define function installed-library-packs () => (library-packs :: ) let library-packs = make(, size: size(*library-packs*)); for (library-pack in *library-packs*, i from 0) library-packs[i] := library-pack end; sort!(library-packs, test: method (info1 :: , info2 :: ) info1.info-name < info2.info-name end) end function installed-library-packs; define class () constant slot info-location :: , required-init-keyword: location:; constant slot info-libraries :: , required-init-keyword: libraries:; constant slot info-examples :: , required-init-keyword: examples:; constant slot info-test-suites :: , required-init-keyword: test-suites:; end class ; define class () end class ; define constant $maximum-library-packs :: = $machine-word-size; define constant $release-library-packs = make(, size: $maximum-library-packs + 1, fill: #f); define function install-numbered-library-pack-info (info :: ) => () let pack :: = info.info-pack-number; assert(pack > 0 & pack <= $maximum-library-packs, "Library pack number must be between 1 and %d, inclusive", $maximum-library-packs); $release-library-packs[pack] := info end function install-numbered-library-pack-info; define method find-library-pack-info (pack :: ) => (info :: false-or()) element(*library-packs*, pack, default: #f) | block (return) for (i :: from 1 to $maximum-library-packs) let info :: false-or() = $release-library-packs[i]; when (info) when (info.info-name = pack) return(info) end end end; return(#f) end end method find-library-pack-info; define method find-library-pack-info (pack :: ) => (info :: false-or()) pack > 0 & pack <= $maximum-library-packs & $release-library-packs[pack] end method find-library-pack-info; define macro library-pack-definer { define library-pack ?pack-name:name ?options:* end } => { let info = make(, name: ?#"pack-name", ?options, title: ?"pack-name" ## " Library Pack", description: "{None}"); install-numbered-library-pack-info(info) } end macro library-pack-definer; define method read-library-pack (locator :: ) => (info :: ) let xml = read-xml-document(locator); let node = xml.document-element; let name = as(, node-attribute(node, "name")); let number = node-attribute(node, "number"); let library-nodes = select-nodes(node, "libraries/library"); let library-pack = make(, name: name, title: node-attribute(node, "title"), number: number & string-to-integer(number), description: select-node-text(node, "description") | "", author: select-node-text(node, "author"), company: select-node-text(node, "company"), copyright: select-node-text(node, "copyright"), location: locator-directory(locator), libraries: map(interpret-library-xml, library-nodes), examples: map(method (node :: ) interpret-library-xml(node, class: ) end, select-nodes(node, "examples/library")), test-suites: map(method (node :: ) interpret-library-xml(node, class: ) end, select-nodes(node, "test-suites/library"))); for (library :: in library-pack.info-libraries, library-node :: in library-nodes) let releases = map(method (release-node :: ) => (info :: ) interpret-library-release-xml(library.info-name, release-node) end, select-nodes(library-node, "release")); if (~empty?(releases)) let this-release = releases[0]; let binary = this-release.info-binary; let binary-name = if (binary) binary.info-binary-name else format-to-string("%s.%s", library.info-name, "dll") end; library.info-library-pack := library-pack; library.info-releases := releases; library.info-binary := binary; library.info-binary-name := binary-name; library.info-database := this-release.info-database; library.info-lib := this-release.info-lib; end end; for (example :: in library-pack.info-examples) example.info-library-pack := library-pack end; for (test-suite :: in library-pack.info-test-suites) test-suite.info-library-pack := library-pack end; library-pack end method read-library-pack; define method interpret-library-xml (node :: , #key class :: subclass() = ) => (info :: ) let sources-node = select-single-node(node, "sources"); let name = as(, node-attribute(node, "name")); make(class, name: name, title: node-attribute(node, "title") | as-lowercase(as(, name)), categories: map(node-text, select-nodes(node, "category")), description: select-node-text(node, "description") | "", source-directory: as(, node-attribute(sources-node, "location")), project: select-node-text(sources-node, "project")) end method interpret-library-xml; define method interpret-library-release-xml (name :: , node :: ) => (info :: ) let binary-node = select-single-node(node, "binary"); make(, product: node-attribute(node, "product"), version: node-attribute(node, "version"), platform: node-attribute(node, "platform"), relative-location: as(, format-to-string("build/%s/Releases/Functional Developer 2.1", name)), binary: binary-node & interpret-library-binary-xml(name, binary-node), database: select-node-text(node, "database") | format-to-string("%s.%s", name, "ddb"), lib: select-node-text(node, "lib") | format-to-string("%s.%s", name, "lib")) end method interpret-library-release-xml; define method interpret-library-binary-xml (name :: , node :: ) => (info :: ) let merge-parent = element(*libraries*, name); make(, binary-name: node-attribute(node, "file"), merged-libraries: map(method (merge-node :: ) let library-name = as(, merge-node.node-text); let library = element(*libraries*, library-name); library.info-merge-parent := merge-parent; library end, select-nodes(node, "merge"))) end method interpret-library-binary-xml; // Install the default library packs if (file-exists?(release-library-packs-directory())) do-directory(method(directory :: , name :: , type) if (type == #"directory") let xml-directory = subdirectory-locator(directory, name); let xml-locator = make(, //---*** UNIX/Linux filesystem is case sensitive; // we need to clean this up! base: as-lowercase(name), extension: $library-pack-extension, directory: xml-directory); if (file-exists?(xml-locator)) read-library-pack(xml-locator); end if; end if; end, release-library-packs-directory()); end if; /// Merged library DLL information define method merged-project-name (library :: ) => (merged-library :: ) let info = find-library-info(library); let merge-parent = info & info.info-merge-parent; if (merge-parent) merge-parent.info-name else library end end method merged-project-name; define method merged-project-libraries (library :: ) => (parent :: , libraries :: ) let library-info = find-library-info(library); let parent-info = if (library-info) library-info.info-merge-parent | library-info end; let parent-binary-info = parent-info & parent-info.info-binary; let parent = if (parent-info) parent-info.info-name else library end; values(parent, if (parent-binary-info) map(info-name, parent-binary-info.info-merged-libraries); else #[] end) end method merged-project-libraries; /// Library category handling define class () constant slot info-name :: , required-init-keyword: name:; constant slot info-title :: , required-init-keyword: title:; constant slot info-subcategories :: = make(), init-keyword: subcategories:; constant slot info-libraries :: = make(), init-keyword: libraries:; end class ; define method info-description (info :: ) => (description :: ) format-to-string("%s libraries", info.info-title) end method info-description; define method installed-library-categories (libraries-function :: ) => (categories :: ) let root-category :: = make(, name: #"root", title: "Root"); for (library-pack-info :: in installed-library-packs()) for (library-info :: in library-pack-info.libraries-function) for (category-name :: in library-info.info-categories) let parent-category :: = root-category; for (level-title :: in %split(category-name, '/')) let level-name :: = as(, level-title); let category :: = block (return) for (category-info :: in parent-category.info-subcategories) if (category-info.info-name == level-name) return(category-info) end end; let category :: = make(, name: level-name, title: level-title); add!(parent-category.info-subcategories, category); category end; parent-category := category end; add!(parent-category.info-libraries, library-info) end end end; root-category.info-subcategories end method installed-library-categories; //---*** andrewa: for bootstrapping only, this really lives in //---*** functional-extensions.dylan. define sealed method %split (string :: , character :: , #key start :: = 0, end: _end :: = string.size, trim? :: = #t) => (strings :: ) let old-position :: = start; let end-position :: = _end; let new-position :: = old-position; let results :: = make(); local method add-substring (start :: , _end :: , #key last? :: = #f) => () if (trim?) while (start < _end & string[start] = ' ') start := start + 1 end; while (start < _end & string[_end - 1] = ' ') _end := _end - 1 end end; // Don't ever return just a single empty string if (~last? | start ~== _end | ~empty?(results)) add!(results, copy-sequence(string, start: start, end: _end)) end end method add-substring; while (new-position < end-position) if (string[new-position] = character) add-substring(old-position, new-position); new-position := new-position + 1; old-position := new-position else new-position := new-position + 1; end end; add-substring(old-position, new-position, last?: #t); results end method %split; /// Examples define class () end class ; define class () end class ; /*--- Remove this when sure... define class () end class ; define class () constant slot info-libraries :: , required-init-keyword: libraries:; end class ; define class () constant slot info-dll-name :: , required-init-keyword: dll-name:; constant slot info-libraries :: , required-init-keyword: libraries:; end class ; define class () slot info-group :: , init-keyword: group:; end class ; define method info-edition (example :: ) => (edition :: ) example.info-group.info-edition end method info-edition; define method info-library-pack (example :: ) => (library-pack :: false-or()) example.info-group.info-library-pack end method info-library-pack; define method info-available? (example :: ) => (available? :: ) info-available?(example.info-group) end method info-available?; define function example-location (info :: ) => (location :: false-or()) let project = info-project(info); let examples-directory = release-examples-directory(); if (project & examples-directory) as(, merge-locators(as(, project), examples-directory)) end end function example-location; /// Example groups define macro example-group-definer { define ?edition:name example-group ?group-name:name (?options:*) ?examples:* end } => { let info = make(, name: ?#"group-name", edition: ?#"edition", examples: vector(?examples), ?options); install-example-group(info) } options: { } => { } { ?option:*, ... } => { ?option, ... } option: { library-pack: ?pack:name } => { library-pack: library-pack-number(?#"pack") } { library-pack: ?pack:expression } => { library-pack: library-pack-number(?pack) } { ?other:* } => { ?other } examples: { } => { } { ?example:*; ... } => { ?example, ... } example: { example ?name:expression, ?example-options:* } => { make(, name: ?#"name", ?example-options) } end macro example-group-definer; define class () constant slot info-edition :: , required-init-keyword: edition:; constant slot info-library-pack :: false-or() = #f, init-keyword: library-pack:; constant slot info-examples :: , required-init-keyword: examples:; end class ; define method initialize (group :: , #key) => () next-method(); let examples = group.info-examples; for (example :: in examples) example.info-group := group end end method initialize; define method info-available? (group :: ) => (available? :: ) let edition = group.info-edition; let library-pack = group.info-library-pack; release-contains-edition?(edition) & (~library-pack | release-contains-library-pack?(library-pack)) end method info-available?; define constant $root-example-group = make(, edition: #"basic", name: #"root-example-group", title: "Examples", description: "Functional Developer example projects", examples: make()); define function release-example-groups () => (groups :: ) convert-example-info($root-example-group, release-examples-directory()); info-examples($root-example-group) end function release-example-groups; define function install-example-group (info :: ) => () let name = info-name(info); let groups = info-examples($root-example-group); remove!(groups, name, test: method (info, name) info-name(info) == name end); add!(groups, info) end function install-example-group; /// DLL groupings define constant $release-dll-groups = make(); define function release-dll-groups () => (groups :: ) $release-dll-groups end function release-dll-groups; define function install-dll-group (info :: ) => () let name = info-name(info); remove!($release-dll-groups, name, test: method (info, name) info-name(info) == name end); add!($release-dll-groups, info) end function install-dll-group; define function find-dll-group-info (name :: ) => (dll-group :: false-or()) block (return) for (dll-group :: in $release-dll-groups) when (info-name(dll-group) == name) return(dll-group) end end end end function find-dll-group-info; define macro dll-group-definer { define dll-group ?group-name:name (?options:*) ?libraries:* end } => { let info = make(, name: ?#"group-name", libraries: vector(?libraries), ?options, title: ?"group-name", description: "{None}"); install-dll-group(info) } libraries: { } => { } { ?library:*; ... } => { ?library, ... } library: { library ?name:expression, ?library-options:* } => { make(, name: ?#"name", ?library-options, title: ?"name", description: "{None}", modules: #[]) } end macro dll-group-definer; define macro renamed-dll-definer { define renamed-dll ?group-name:name = ?dll-name:expression } => { let info = make(, name: ?#"group-name", title: ?"group-name", dll-name: ?dll-name, libraries: #[], description: "{None}"); install-dll-group(info) } end macro renamed-dll-definer; define macro renamed-dlls-definer { define renamed-dlls () ?renamed-dll:* end } => { ?renamed-dll } renamed-dll: { } => { } { ?group-name:name = ?dll-name:expression; ... } => { define renamed-dll ?group-name = ?dll-name; ... } end macro renamed-dlls-definer; /// Library Groups define constant $release-library-groups = make(); define function release-library-groups () => (groups :: ) $release-library-groups end function release-library-groups; define function install-library-group (info :: ) => () let name = info-name(info); remove!($release-library-groups, name, test: method (info, name) info-name(info) == name end); add!($release-library-groups, info) end function install-library-group; define macro library-group-definer { define library-group ?group-name:name (?options:*) ?libraries:* end } => { let info = make(, name: ?#"group-name", libraries: vector(?libraries), ?options); install-library-group(info) } libraries: { } => { } { ?library:*; ... } => { ?library, ... } library: { library ?name:expression, ?library-options:* } => { make(, name: ?#"name", ?library-options) } end macro library-group-definer; */