(* :Title: Package Utilities *)

(* :Context: Utilities`Package` *)

(* :Author: John M. Novak *)

(* :Summary: 
This package provides various utilities for
searching and working with Mathematica packages.
*)

(* :Package Version: 1.0.1 *)

(* :Mathematica Version: 2.1 *)

(* :Copyright: Copyright 1992-1999, Wolfram Research, Inc.  *)

(* :History:
	V1.0 June 1992, by John M. Novak.
	V1.0.1 February 1998, by John M. Novak - quick fix for handling
	   of pathname separator.
*)

(* :Keywords:
	packages, path, keywords
*)

(* :Limitations:  *)

(* :Discussion:
	
*)

BeginPackage["Utilities`Package`"]

FindPackages::usage =
"FindPackages[path] finds all .m files on the given path.
FindPackages[path, \"pattern\"] finds all packages matching
the given string pattern on the path.  FindPackages[] finds
all packages on $Path. The path can be specified as a directory
or a list of directories.";

Annotation::usage =
"Annotation[package] finds all the keywords in the annotation
comment fields from the named package. Annotation[package, keyword]
returns the contents of the given annotation comment field.
Annotation[package, {key1, key2,...}] returns the contents of
several named fields.";

FullPath::usage =
"FullPath is an option to the FindPackages function.  When
set to False (the default), FindPackages returns file names in
context form.  When True, full path names are returned.";

Begin["`Private`"]

(* keyword stuff *)

(* allow multiple files *)

Annotation[files:{(_String | _InputStream)..}, args___] :=
	Map[Annotation[#, args]&, files]

(* if given a context, turn into a file name *)

Annotation[context_String?(StringTake[context,-1] === "`" &),
		tags___] :=
	Annotation[ContextToFilename[context], tags]

(* if only file name, find names of keywords present *)

Annotation[file:(_String | _InputStream)] :=
	Map[StringTake[#,
			Take[First[Transpose[StringPosition[#,":"]]],2] +
				{1, -1}]&,
		FindList[file,{"(* :", "(*:"}, AnchoredSearch -> True]]

(* if only one tag, make list *)

Annotation[s_, tag_String] :=
	Annotation[s, {tag}]

(* if a file name instead of stream, open stream *)

Annotation[file_String, tags:{__String}] :=
	Module[{stream, out},
		stream = OpenRead[file];
		out = Annotation[stream, tags];
		Close[stream];
		out
	]

(* find the fields *)

Annotation[stream_InputStream, tags:{__String}] :=
	Module[{str, out = {}},
		While[Head[str = Find[stream,
					Flatten[
						Map[{"(* :"<>#<>":", "(*:"<>#<>":"}&,
							tags]
					],
					AnchoredSearch -> True]] ===
				String,
			AppendTo[out, gettagfield[stream, str]]
		];
		out
	]

(* pull out an individual field - current position in
stream is end of tag.  Go back to beginning of tag, read in
expression - this is the comment, though the read routine is
stupid enough not to return the comment, even when held.  Now
you know where the end of the comment is; go back to the
beginning of the comment, read characters until the end of
the comment, and join them into a string. *)

gettagfield[stream_InputStream, tag_String] :=
	Module[{start, finish},
		start = SetStreamPosition[stream,
			StreamPosition[stream] - StringLength[tag]];
		Read[stream, Expression];
		finish = StreamPosition[stream];
		SetStreamPosition[stream, start];
		StringJoin[ReadList[stream, Character, finish - start]]
	]

(* FindPackages on path routine *)

Options[FindPackages] =
	{FullPath -> False};

FindPackages[opts:((_Rule | _RuleDelayed)...)] :=
	FindPackages[$Path, opts]

FindPackages[path:{_String..}, pat___] :=
	Map[FindPackages[#, pat]&, path]

FindPackages[path_String, pattern_String,
		opts:((_Rule | _RuleDelayed)...)] :=
	packages[path, pattern<>".m", opts]

FindPackages[path_String, pattern:{_String..},
		opts:((_Rule | _RuleDelayed)...)] :=
	packages[path, Map[#<>".m"&, pattern], opts]

FindPackages[path_String, opts:((_Rule | _RuleDelayed)...)] :=
	packages[path, "*.m", opts]

packages[path_, pattern_, opts___] :=
	Module[{cform, names},
		{cform} = {FullPath}/.{opts}/.Options[FindPackages];
		names = Select[FileNames[pattern, path,2],
			(FileType[#] === File)&];
		If[!TrueQ[cform],
			Map[StringReplace[
					StringDrop[#, StringLength[path] + 1],
					{$PathnameSeparator -> "`", ".m" -> "`"}]&,
				names],
		(* else *)
			names
		]
	]

End[]

EndPackage[]
