aboutsummaryrefslogtreecommitdiffstats
path: root/lib/edoc/src
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/edoc/src
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/edoc/src')
-rw-r--r--lib/edoc/src/Makefile91
-rw-r--r--lib/edoc/src/edoc.app.src24
-rw-r--r--lib/edoc/src/edoc.appup.src1
-rw-r--r--lib/edoc/src/edoc.erl771
-rw-r--r--lib/edoc/src/edoc.hrl100
-rw-r--r--lib/edoc/src/edoc_data.erl545
-rw-r--r--lib/edoc/src/edoc_doclet.erl521
-rw-r--r--lib/edoc/src/edoc_extract.erl584
-rw-r--r--lib/edoc/src/edoc_layout.erl875
-rw-r--r--lib/edoc/src/edoc_lib.erl998
-rw-r--r--lib/edoc/src/edoc_macros.erl327
-rw-r--r--lib/edoc/src/edoc_parser.yrl423
-rw-r--r--lib/edoc/src/edoc_refs.erl217
-rw-r--r--lib/edoc/src/edoc_report.erl96
-rw-r--r--lib/edoc/src/edoc_run.erl225
-rw-r--r--lib/edoc/src/edoc_scanner.erl358
-rw-r--r--lib/edoc/src/edoc_tags.erl373
-rw-r--r--lib/edoc/src/edoc_types.erl204
-rw-r--r--lib/edoc/src/edoc_types.hrl130
-rw-r--r--lib/edoc/src/edoc_wiki.erl456
-rw-r--r--lib/edoc/src/otpsgml_layout.erl853
21 files changed, 8172 insertions, 0 deletions
diff --git a/lib/edoc/src/Makefile b/lib/edoc/src/Makefile
new file mode 100644
index 0000000000..fd0fbac37d
--- /dev/null
+++ b/lib/edoc/src/Makefile
@@ -0,0 +1,91 @@
+#
+# Copyright (C) 2004, Ericsson Telecommunications
+# Author: Richard Carlsson, Bertil Karlsson
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../vsn.mk
+VSN=$(EDOC_VSN)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/edoc-$(VSN)
+
+
+#
+# Common Macros
+#
+
+EBIN = ../ebin
+XMERL = ../../xmerl
+ERL_COMPILE_FLAGS += -I../include -I$(XMERL)/include +warn_unused_vars +nowarn_shadow_vars +warn_unused_import +warn_deprecated_guard
+
+SOURCES= \
+ edoc.erl edoc_data.erl edoc_doclet.erl edoc_extract.erl \
+ edoc_layout.erl edoc_lib.erl edoc_macros.erl edoc_parser.erl \
+ edoc_refs.erl edoc_report.erl edoc_run.erl edoc_scanner.erl \
+ edoc_tags.erl edoc_types.erl edoc_wiki.erl otpsgml_layout.erl
+
+OBJECTS=$(SOURCES:%.erl=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET)
+
+HRL_FILES = edoc.hrl edoc_types.hrl ../include/edoc_doclet.hrl
+
+YRL_FILE = edoc_parser.yrl
+
+APP_FILE= edoc.app
+APP_SRC= $(APP_FILE).src
+APP_TARGET= $(EBIN)/$(APP_FILE)
+
+APPUP_FILE= edoc.appup
+APPUP_SRC= $(APPUP_FILE).src
+APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug opt: $(OBJECTS)
+
+all: $(OBJECTS)
+
+$(OBJECTS): $(HRL_FILES) $(XMERL)/include/xmerl.hrl
+
+clean:
+ rm -f $(OBJECTS) edoc_parser.erl
+ rm -f core *~
+
+distclean: clean
+
+realclean: clean
+
+$(EBIN)/%.$(EMULATOR):%.erl
+ erlc -W $(ERL_COMPILE_FLAGS) -o$(EBIN) $<
+
+# ----------------------------------------------------
+# Special Build Targets
+# ----------------------------------------------------
+
+$(APP_TARGET): $(APP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DATA) $(OBJECTS) $(RELSYSDIR)/ebin
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DATA) $(SOURCES) $(HRL_FILES) $(YRL_FILE) $(RELSYSDIR)/src
+
+release_docs_spec:
+
diff --git a/lib/edoc/src/edoc.app.src b/lib/edoc/src/edoc.app.src
new file mode 100644
index 0000000000..2177533441
--- /dev/null
+++ b/lib/edoc/src/edoc.app.src
@@ -0,0 +1,24 @@
+% This is an -*- erlang -*- file.
+
+{application, edoc,
+ [{description, "EDoc"},
+ {vsn, "%VSN%"},
+ {modules, [edoc,
+ edoc_data,
+ edoc_doclet,
+ edoc_extract,
+ edoc_layout,
+ edoc_lib,
+ edoc_macros,
+ edoc_parser,
+ edoc_refs,
+ edoc_report,
+ edoc_run,
+ edoc_scanner,
+ edoc_tags,
+ edoc_types,
+ edoc_wiki,
+ otpsgml_layout]},
+ {registered,[]},
+ {applications, [compiler,kernel,stdlib,syntax_tools]},
+ {env, []}]}.
diff --git a/lib/edoc/src/edoc.appup.src b/lib/edoc/src/edoc.appup.src
new file mode 100644
index 0000000000..54a63833e6
--- /dev/null
+++ b/lib/edoc/src/edoc.appup.src
@@ -0,0 +1 @@
+{"%VSN%",[],[]}.
diff --git a/lib/edoc/src/edoc.erl b/lib/edoc/src/edoc.erl
new file mode 100644
index 0000000000..ec452a5929
--- /dev/null
+++ b/lib/edoc/src/edoc.erl
@@ -0,0 +1,771 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @copyright 2001-2007 Richard Carlsson
+%% @author Richard Carlsson <[email protected]>
+%% @version {@version}
+%% @end
+%% =====================================================================
+
+%% TODO: check weirdness in name generation for @spec f(TypeName, ...) -> ...
+%% TODO: option for ignoring functions matching some pattern ('..._test_'/0)
+%% TODO: @private_type tag, opaque unless generating private docs?
+%% TODO: document the record type syntax
+%% TODO: some 'skip' option for ignoring particular modules/packages?
+%% TODO: intermediate-level packages: document even if no local sources.
+%% TODO: multiline comment support (needs modified comment representation)
+%% TODO: config-file for default settings
+%% TODO: config: locations of all local docdirs; generate local doc-index page
+%% TODO: config: URL:s of offline packages/apps
+%% TODO: config: default stylesheet
+%% TODO: config: default header/footer, etc.
+%% TODO: offline linkage
+%% TODO: including source code, explicitly and/or automatically
+
+%% @doc EDoc - the Erlang program documentation generator.
+%%
+%% This module provides the main user interface to EDoc.
+%% <ul>
+%% <li><a href="overview-summary.html">EDoc User Manual</a></li>
+%% <li><a href="overview-summary.html#Running_EDoc">Running EDoc</a></li>
+%% </ul>
+
+-module(edoc).
+
+-export([packages/1, packages/2, files/1, files/2,
+ application/1, application/2, application/3,
+ toc/1, toc/2, toc/3,
+ run/3,
+ file/1, file/2,
+ read/1, read/2,
+ layout/1, layout/2,
+ get_doc/1, get_doc/2, get_doc/3,
+ read_comments/1, read_comments/2,
+ read_source/1, read_source/2]).
+
+-import(edoc_report, [report/2, report/3, error/1, error/3]).
+
+-include("edoc.hrl").
+
+
+%% @spec (Name::filename()) -> ok
+%% @equiv file(Name, [])
+%% @deprecated See {@link file/2} for details.
+
+file(Name) ->
+ file(Name, []).
+
+%% @spec file(filename(), proplist()) -> ok
+%%
+%% @type filename() = //kernel/file:filename()
+%% @type proplist() = [term()]
+%%
+%% @deprecated This is part of the old interface to EDoc and is mainly
+%% kept for backwards compatibility. The preferred way of generating
+%% documentation is through one of the functions {@link application/2},
+%% {@link packages/2} and {@link files/2}.
+%%
+%% @doc Reads a source code file and outputs formatted documentation to
+%% a corresponding file.
+%%
+%% Options:
+%% <dl>
+%% <dt>{@type {dir, filename()@}}
+%% </dt>
+%% <dd>Specifies the output directory for the created file. (By
+%% default, the output is written to the directory of the source
+%% file.)
+%% </dd>
+%% <dt>{@type {source_suffix, string()@}}
+%% </dt>
+%% <dd>Specifies the expected suffix of the input file. The default
+%% value is `".erl"'.
+%% </dd>
+%% <dt>{@type {file_suffix, string()@}}
+%% </dt>
+%% <dd>Specifies the suffix for the created file. The default value is
+%% `".html"'.
+%% </dd>
+%% </dl>
+%%
+%% See {@link get_doc/2} and {@link layout/2} for further
+%% options.
+%%
+%% For running EDoc from a Makefile or similar, see
+%% {@link edoc_run:file/1}.
+%%
+%% @see read/2
+
+%% NEW-OPTIONS: source_suffix, file_suffix, dir
+%% INHERIT-OPTIONS: read/2
+
+file(Name, Options) ->
+ Text = read(Name, Options),
+ SrcSuffix = proplists:get_value(source_suffix, Options,
+ ?DEFAULT_SOURCE_SUFFIX),
+ BaseName = filename:basename(Name, SrcSuffix),
+ Suffix = proplists:get_value(file_suffix, Options,
+ ?DEFAULT_FILE_SUFFIX),
+ Dir = proplists:get_value(dir, Options, filename:dirname(Name)),
+ edoc_lib:write_file(Text, Dir, BaseName ++ Suffix).
+
+
+%% TODO: better documentation of files/1/2, packages/1/2, application/1/2/3
+
+%% @spec (Files::[filename() | {package(), [filename()]}]) -> ok
+%% @equiv packages(Packages, [])
+
+files(Files) ->
+ files(Files, []).
+
+%% @spec (Files::[filename() | {package(), [filename()]}],
+%% Options::proplist()) -> ok
+%% @doc Runs EDoc on a given set of source files. See {@link run/3} for
+%% details, including options.
+%% @equiv run([], Files, Options)
+
+files(Files, Options) ->
+ run([], Files, Options).
+
+%% @spec (Packages::[package()]) -> ok
+%% @equiv packages(Packages, [])
+
+packages(Packages) ->
+ packages(Packages, []).
+
+%% @spec (Packages::[package()], Options::proplist()) -> ok
+%% @type package() = atom() | string()
+%%
+%% @doc Runs EDoc on a set of packages. The `source_path' option is used
+%% to locate the files; see {@link run/3} for details, including
+%% options. This function automatically appends the current directory to
+%% the source path.
+%%
+%% @equiv run(Packages, [], Options)
+
+packages(Packages, Options) ->
+ run(Packages, [], Options ++ [{source_path, [?CURRENT_DIR]}]).
+
+%% @spec (Application::atom()) -> ok
+%% @equiv application(Application, [])
+
+application(App) ->
+ application(App, []).
+
+%% @spec (Application::atom(), Options::proplist()) -> ok
+%% @doc Run EDoc on an application in its default app-directory. See
+%% {@link application/3} for details.
+%% @see application/1
+
+application(App, Options) when is_atom(App) ->
+ case code:lib_dir(App) of
+ Dir when is_list(Dir) ->
+ application(App, Dir, Options);
+ _ ->
+ report("cannot find application directory for '~s'.",
+ [App]),
+ exit(error)
+ end.
+
+%% @spec (Application::atom(), Dir::filename(), Options::proplist())
+%% -> ok
+%% @doc Run EDoc on an application located in the specified directory.
+%% Tries to automatically set up good defaults. Unless the user
+%% specifies otherwise:
+%% <ul>
+%% <li>The `doc' subdirectory will be used as the target directory, if
+%% it exists; otherwise the application directory is used.
+%% </li>
+%% <li>The source code is assumed to be located in the `src'
+%% subdirectory, if it exists, or otherwise in the application
+%% directory itself.
+%% </li>
+%% <li>The {@link run/3. `subpackages'} option is turned on. All found
+%% source files will be processed.
+%% </li>
+%% <li>The `include' subdirectory is automatically added to the
+%% include path. (Only important if {@link read_source/2.
+%% preprocessing} is turned on.)
+%% </li>
+%% </ul>
+%%
+%% See {@link run/3} for details, including options.
+%%
+%% @see application/2
+
+application(App, Dir, Options) when is_atom(App) ->
+ Src = edoc_lib:try_subdir(Dir, ?SOURCE_DIR),
+ Overview = filename:join(edoc_lib:try_subdir(Dir, ?EDOC_DIR),
+ ?OVERVIEW_FILE),
+ Opts = Options ++ [{source_path, [Src]},
+ subpackages,
+ {title, io_lib:fwrite("The ~s application", [App])},
+ {overview, Overview},
+ {dir, filename:join(Dir, ?EDOC_DIR)},
+ {includes, [filename:join(Dir, "include")]}],
+ Opts1 = set_app_default(App, Dir, Opts),
+ %% Recursively document all subpackages of '' - i.e., everything.
+ run([''], [], [{application, App} | Opts1]).
+
+%% Try to set up a default application base URI in a smart way if the
+%% user has not specified it explicitly.
+
+set_app_default(App, Dir0, Opts) ->
+ case proplists:get_value(app_default, Opts) of
+ undefined ->
+ AppName = atom_to_list(App),
+ Dir = edoc_lib:simplify_path(filename:absname(Dir0)),
+ AppDir = case filename:basename(Dir) of
+ AppName ->
+ filename:dirname(Dir);
+ _ ->
+ ?APP_DEFAULT
+ end,
+ [{app_default, AppDir} | Opts];
+ _ ->
+ Opts
+ end.
+
+%% If no source files are found for a (specified) package, no package
+%% documentation will be generated either (even if there is a
+%% package-documentation file). This is the way it should be. For
+%% specified files, use empty package (unless otherwise specified). The
+%% assumed package is always used for creating the output. If the actual
+%% module or package of the source differs from the assumption gathered
+%% from the path and file name, a warning should be issued (since links
+%% are likely to be incorrect).
+
+opt_defaults() ->
+ [packages].
+
+opt_negations() ->
+ [{no_preprocess, preprocess},
+ {no_subpackages, subpackages},
+ {no_packages, packages}].
+
+%% @spec run(Packages::[package()],
+%% Files::[filename() | {package(), [filename()]}],
+%% Options::proplist()) -> ok
+%% @doc Runs EDoc on a given set of source files and/or packages. Note
+%% that the doclet plugin module has its own particular options; see the
+%% `doclet' option below.
+%%
+%% Also see {@link layout/2} for layout-related options, and
+%% {@link get_doc/2} for options related to reading source
+%% files.
+%%
+%% Options:
+%% <dl>
+%% <dt>{@type {app_default, string()@}}
+%% </dt>
+%% <dd>Specifies the default base URI for unknown applications.
+%% </dd>
+%% <dt>{@type {application, App::atom()@}}
+%% </dt>
+%% <dd>Specifies that the generated documentation describes the
+%% application `App'. This mainly affects generated references.
+%% </dd>
+%% <dt>{@type {dir, filename()@}}
+%% </dt>
+%% <dd>Specifies the target directory for the generated documentation.
+%% </dd>
+%% <dt>{@type {doc_path, [string()]@}}
+%% </dt>
+%% <dd>Specifies a list of URI:s pointing to directories that contain
+%% EDoc-generated documentation. URI without a `scheme://' part are
+%% taken as relative to `file://'. (Note that such paths must use
+%% `/' as separator, regardless of the host operating system.)
+%% </dd>
+%% <dt>{@type {doclet, Module::atom()@}}
+%% </dt>
+%% <dd>Specifies a callback module to be used for creating the
+%% documentation. The module must export a function `run(Cmd, Ctxt)'.
+%% The default doclet module is {@link edoc_doclet}; see {@link
+%% edoc_doclet:run/2} for doclet-specific options.
+%% </dd>
+%% <dt>{@type {exclude_packages, [package()]@}}
+%% </dt>
+%% <dd>Lists packages to be excluded from the documentation. Typically
+%% used in conjunction with the `subpackages' option.
+%% </dd>
+%% <dt>{@type {file_suffix, string()@}}
+%% </dt>
+%% <dd>Specifies the suffix used for output files. The default value is
+%% `".html"'. Note that this also affects generated references.
+%% </dd>
+%% <dt>{@type {new, bool()@}}
+%% </dt>
+%% <dd>If the value is `true', any existing `edoc-info' file in the
+%% target directory will be ignored and overwritten. The default
+%% value is `false'.
+%% </dd>
+%% <dt>{@type {packages, bool()@}}
+%% </dt>
+%% <dd>If the value is `true', it it assumed that packages (module
+%% namespaces) are being used, and that the source code directory
+%% structure reflects this. The default value is `true'. (Usually,
+%% this does the right thing even if all the modules belong to the
+%% top-level "empty" package.) `no_packages' is an alias for
+%% `{packages, false}'. See the `subpackages' option below for
+%% further details.
+%%
+%% If the source code is organized in a hierarchy of
+%% subdirectories although it does not use packages, use
+%% `no_packages' together with the recursive-search `subpackages'
+%% option (on by default) to automatically generate documentation
+%% for all the modules.
+%% </dd>
+%% <dt>{@type {source_path, [filename()]@}}
+%% </dt>
+%% <dd>Specifies a list of file system paths used to locate the source
+%% code for packages.
+%% </dd>
+%% <dt>{@type {source_suffix, string()@}}
+%% </dt>
+%% <dd>Specifies the expected suffix of input files. The default
+%% value is `".erl"'.
+%% </dd>
+%% <dt>{@type {subpackages, bool()@}}
+%% </dt>
+%% <dd>If the value is `true', all subpackages of specified packages
+%% will also be included in the documentation. The default value is
+%% `false'. `no_subpackages' is an alias for `{subpackages,
+%% false}'. See also the `exclude_packages' option.
+%%
+%% Subpackage source files are found by recursively searching
+%% for source code files in subdirectories of the known source code
+%% root directories. (Also see the `source_path' option.) Directory
+%% names must begin with a lowercase letter and contain only
+%% alphanumeric characters and underscore, or they will be ignored.
+%% (For example, a subdirectory named `test-files' will not be
+%% searched.)
+%% </dd>
+%% </dl>
+%%
+%% @see files/2
+%% @see packages/2
+%% @see application/2
+
+%% NEW-OPTIONS: source_path, application
+%% INHERIT-OPTIONS: init_context/1
+%% INHERIT-OPTIONS: expand_sources/2
+%% INHERIT-OPTIONS: target_dir_info/5
+%% INHERIT-OPTIONS: edoc_lib:find_sources/3
+%% INHERIT-OPTIONS: edoc_lib:run_doclet/2
+%% INHERIT-OPTIONS: edoc_lib:get_doc_env/4
+
+run(Packages, Files, Opts0) ->
+ Opts = expand_opts(Opts0),
+ Ctxt = init_context(Opts),
+ Dir = Ctxt#context.dir,
+ Path = proplists:append_values(source_path, Opts),
+ Ss = sources(Path, Packages, Opts),
+ {Ss1, Ms} = expand_sources(expand_files(Files) ++ Ss, Opts),
+ Ps = [P || {_, P, _, _} <- Ss1],
+ App = proplists:get_value(application, Opts, ?NO_APP),
+ {App1, Ps1, Ms1} = target_dir_info(Dir, App, Ps, Ms, Opts),
+ %% The "empty package" is never included in the list of packages.
+ Ps2 = edoc_lib:unique(lists:sort(Ps1)) -- [''],
+ Ms2 = edoc_lib:unique(lists:sort(Ms1)),
+ Fs = package_files(Path, Ps2),
+ Env = edoc_lib:get_doc_env(App1, Ps2, Ms2, Opts),
+ Ctxt1 = Ctxt#context{env = Env},
+ Cmd = #doclet_gen{sources = Ss1,
+ app = App1,
+ packages = Ps2,
+ modules = Ms2,
+ filemap = Fs
+ },
+ F = fun (M) ->
+ M:run(Cmd, Ctxt1)
+ end,
+ edoc_lib:run_doclet(F, Opts).
+
+expand_opts(Opts0) ->
+ proplists:substitute_negations(opt_negations(),
+ Opts0 ++ opt_defaults()).
+
+%% NEW-OPTIONS: dir
+%% DEFER-OPTIONS: run/3
+
+init_context(Opts) ->
+ #context{dir = proplists:get_value(dir, Opts, ?CURRENT_DIR),
+ opts = Opts
+ }.
+
+%% INHERIT-OPTIONS: edoc_lib:find_sources/3
+
+sources(Path, Packages, Opts) ->
+ lists:foldl(fun (P, Xs) ->
+ edoc_lib:find_sources(Path, P, Opts) ++ Xs
+ end,
+ [], Packages).
+
+package_files(Path, Packages) ->
+ Name = ?PACKAGE_FILE, % this is hard-coded for now
+ D = lists:foldl(fun (P, D) ->
+ F = edoc_lib:find_file(Path, P, Name),
+ dict:store(P, F, D)
+ end,
+ dict:new(), Packages),
+ fun (P) ->
+ case dict:find(P, D) of
+ {ok, F} -> F;
+ error -> ""
+ end
+ end.
+
+%% Expand user-specified sets of files.
+
+expand_files([{P, Fs1} | Fs]) ->
+ [{P, filename:basename(F), filename:dirname(F)} || F <- Fs1]
+ ++ expand_files(Fs);
+expand_files([F | Fs]) ->
+ [{'', filename:basename(F), filename:dirname(F)} |
+ expand_files(Fs)];
+expand_files([]) ->
+ [].
+
+%% Create the (assumed) full module names. Keep only the first source
+%% for each module, but preserve the order of the list.
+
+%% NEW-OPTIONS: source_suffix, packages
+%% DEFER-OPTIONS: run/3
+
+expand_sources(Ss, Opts) ->
+ Suffix = proplists:get_value(source_suffix, Opts,
+ ?DEFAULT_SOURCE_SUFFIX),
+ Ss1 = case proplists:get_bool(packages, Opts) of
+ true -> Ss;
+ false -> [{'',F,D} || {_P,F,D} <- Ss]
+ end,
+ expand_sources(Ss1, Suffix, sets:new(), [], []).
+
+expand_sources([{P, F, D} | Fs], Suffix, S, As, Ms) ->
+ M = list_to_atom(packages:concat(P, filename:rootname(F, Suffix))),
+ case sets:is_element(M, S) of
+ true ->
+ expand_sources(Fs, Suffix, S, As, Ms);
+ false ->
+ S1 = sets:add_element(M, S),
+ expand_sources(Fs, Suffix, S1, [{M, P, F, D} | As],
+ [M | Ms])
+ end;
+expand_sources([], _Suffix, _S, As, Ms) ->
+ {lists:reverse(As), lists:reverse(Ms)}.
+
+%% NEW-OPTIONS: new
+
+target_dir_info(Dir, App, Ps, Ms, Opts) ->
+ case proplists:get_bool(new, Opts) of
+ true ->
+ {App, Ps, Ms};
+ false ->
+ {App1, Ps1, Ms1} = edoc_lib:read_info_file(Dir),
+ {if App == ?NO_APP -> App1;
+ true -> App
+ end,
+ Ps ++ Ps1,
+ Ms ++ Ms1}
+ end.
+
+
+%% @hidden Not official yet
+
+toc(Dir) ->
+ toc(Dir, []).
+
+%% @equiv toc(Dir, Paths, [])
+%% @hidden Not official yet
+
+%% NEW-OPTIONS: doc_path
+
+toc(Dir, Opts) ->
+ Paths = proplists:append_values(doc_path, Opts)
+ ++ edoc_lib:find_doc_dirs(),
+ toc(Dir, Paths, Opts).
+
+%% @doc Create a meta-level table of contents.
+%% @hidden Not official yet
+
+%% INHERIT-OPTIONS: init_context/1
+%% INHERIT-OPTIONS: edoc_lib:run_doclet/2
+%% INHERIT-OPTIONS: edoc_lib:get_doc_env/4
+
+toc(Dir, Paths, Opts0) ->
+ Opts = expand_opts(Opts0 ++ [{dir, Dir}]),
+ Ctxt = init_context(Opts),
+ Env = edoc_lib:get_doc_env('', [], [], Opts),
+ Ctxt1 = Ctxt#context{env = Env},
+ F = fun (M) ->
+ M:run(#doclet_toc{paths=Paths}, Ctxt1)
+ end,
+ edoc_lib:run_doclet(F, Opts).
+
+
+%% @spec read(File::filename()) -> string()
+%% @equiv read(File, [])
+
+read(File) ->
+ read(File, []).
+
+%% @spec read(File::filename(), Options::proplist()) -> string()
+%%
+%% @doc Reads and processes a source file and returns the resulting
+%% EDoc-text as a string. See {@link get_doc/2} and {@link layout/2} for
+%% options.
+%%
+%% @see file/2
+
+%% INHERIT-OPTIONS: get_doc/2, layout/2
+
+read(File, Opts) ->
+ {_ModuleName, Doc} = get_doc(File, Opts),
+ layout(Doc, Opts).
+
+
+%% @spec layout(Doc::edoc_module()) -> string()
+%% @equiv layout(Doc, [])
+
+layout(Doc) ->
+ layout(Doc, []).
+
+%% @spec layout(Doc::edoc_module(), Options::proplist()) -> string()
+%%
+%% @doc Transforms EDoc module documentation data to text. The default
+%% layout creates an HTML document.
+%%
+%% Options:
+%% <dl>
+%% <dt>{@type {layout, Module::atom()@}}
+%% </dt>
+%% <dd>Specifies a callback module to be used for formatting. The
+%% module must export a function `module(Doc, Options)'. The
+%% default callback module is {@link edoc_layout}; see {@link
+%% edoc_layout:module/2} for layout-specific options.
+%% </dd>
+%% </dl>
+%%
+%% @see layout/1
+%% @see run/3
+%% @see read/2
+%% @see file/2
+
+%% INHERIT-OPTIONS: edoc_lib:run_layout/2
+
+layout(Doc, Opts) ->
+ F = fun (M) ->
+ M:module(Doc, Opts)
+ end,
+ edoc_lib:run_layout(F, Opts).
+
+
+%% @spec (File) -> [comment()]
+%% @equiv read_comments(File, [])
+
+read_comments(File) ->
+ read_comments(File, []).
+
+%% @spec read_comments(File::filename(), Options::proplist()) ->
+%% [comment()]
+%% where
+%% comment() = {Line, Column, Indentation, Text},
+%% Line = integer(),
+%% Column = integer(),
+%% Indentation = integer(),
+%% Text = [string()]
+%%
+%% @doc Extracts comments from an Erlang source code file. See the
+%% module {@link //syntax_tools/erl_comment_scan} for details on the
+%% representation of comments. Currently, no options are avaliable.
+
+read_comments(File, _Opts) ->
+ erl_comment_scan:file(File).
+
+
+%% @spec (File) -> [syntaxTree()]
+%% @equiv read_source(File, [])
+
+read_source(Name) ->
+ read_source(Name, []).
+
+%% @spec read_source(File::filename(), Options::proplist()) ->
+%% [syntaxTree()]
+%%
+%% @type syntaxTree() = //syntax_tools/erl_syntax:syntaxTree()
+%%
+%% @doc Reads an Erlang source file and returns the list of "source code
+%% form" syntax trees.
+%%
+%% Options:
+%% <dl>
+%% <dt>{@type {preprocess, bool()@}}
+%% </dt>
+%% <dd>If the value is `true', the source file will be read via the
+%% Erlang preprocessor (`epp'). The default value is `false'.
+%% `no_preprocess' is an alias for `{preprocess, false}'.
+%%
+%% Normally, preprocessing is not necessary for EDoc to work, but
+%% if a file contains too exotic definitions or uses of macros, it
+%% will not be possible to read it without preprocessing. <em>Note:
+%% comments in included files will not be available to EDoc, even
+%% with this option enabled.</em>
+%% </dd>
+%% <dt>{@type {includes, Path::[string()]@}}
+%% </dt>
+%% <dd>Specifies a list of directory names to be searched for include
+%% files, if the `preprocess' option is turned on. Also used with
+%% the `@headerfile' tag. The default value is the empty list. The
+%% directory of the source file is always automatically appended to
+%% the search path.
+%% </dd>
+%% <dt>{@type {macros, [{atom(), term()@}]@}}
+%% </dt>
+%% <dd>Specifies a list of pre-defined Erlang preprocessor (`epp')
+%% macro definitions, used if the `preprocess' option is turned on.
+%% The default value is the empty list.</dd>
+%% </dl>
+%%
+%% @see get_doc/2
+%% @see //syntax_tools/erl_syntax
+
+%% NEW-OPTIONS: [no_]preprocess (preprocess -> includes, macros)
+
+read_source(Name, Opts0) ->
+ Opts = expand_opts(Opts0),
+ case read_source_1(Name, Opts) of
+ {ok, Forms} ->
+ check_forms(Forms, Name),
+ Forms;
+ {error, R} ->
+ error({"error reading file '~s'.",
+ [edoc_lib:filename(Name)]}),
+ exit({error, R})
+ end.
+
+read_source_1(Name, Opts) ->
+ case proplists:get_bool(preprocess, Opts) of
+ true ->
+ read_source_2(Name, Opts);
+ false ->
+ epp_dodger:quick_parse_file(Name, Opts ++ [{no_fail, false}])
+ end.
+
+read_source_2(Name, Opts) ->
+ Includes = proplists:append_values(includes, Opts)
+ ++ [filename:dirname(Name)],
+ Macros = proplists:append_values(macros, Opts),
+ epp:parse_file(Name, Includes, Macros).
+
+check_forms(Fs, Name) ->
+ Fun = fun (F) ->
+ case erl_syntax:type(F) of
+ error_marker ->
+ case erl_syntax:error_marker_info(F) of
+ {L, M, D} ->
+ error(L, Name, {format_error, M, D});
+
+ Other ->
+ report(Name, "unknown error in "
+ "source code: ~w.", [Other])
+ end,
+ exit(error);
+ _ ->
+ ok
+ end
+ end,
+ lists:foreach(Fun, Fs).
+
+
+%% @spec get_doc(File::filename()) -> {ModuleName, edoc_module()}
+%% @equiv get_doc(File, [])
+
+get_doc(File) ->
+ get_doc(File, []).
+
+%% @spec get_doc(File::filename(), Options::proplist()) ->
+%% {ModuleName, edoc_module()}
+%% ModuleName = atom()
+%%
+%% @type edoc_module(). The EDoc documentation data for a module,
+%% expressed as an XML document in {@link //xmerl. XMerL} format. See
+%% the file <a href="../priv/edoc.dtd">`edoc.dtd'</a> for details.
+%%
+%% @doc Reads a source code file and extracts EDoc documentation data.
+%% Note that without an environment parameter (see {@link get_doc/3}),
+%% hypertext links may not be correct.
+%%
+%% Options:
+%% <dl>
+%% <dt>{@type {def, Macros@}}
+%% </dt>
+%% <dd><ul>
+%% <li>`Macros' = {@type Macro | [Macro]}</li>
+%% <li>`Macro' = {@type {Name::atom(), Text::string()@}}</li>
+%% </ul>
+%% Specifies a set of EDoc macro definitions. See
+%% <a href="overview-summary.html#Macro_expansion">Inline macro expansion</a>
+%% for details.
+%% </dd>
+%% <dt>{@type {hidden, bool()@}}
+%% </dt>
+%% <dd>If the value is `true', documentation of hidden functions will
+%% also be included. The default value is `false'.
+%% </dd>
+%% <dt>{@type {private, bool()@}}
+%% </dt>
+%% <dd>If the value is `true', documentation of private functions will
+%% also be included. The default value is `false'.
+%% </dd>
+%% <dt>{@type {todo, bool()@}}
+%% </dt>
+%% <dd>If the value is `true', To-Do notes written using `@todo' or
+%% `@TODO' tags will be included in the documentation. The default
+%% value is `false'.
+%% </dd>
+%% </dl>
+%%
+%% See {@link read_source/2}, {@link read_comments/2} and {@link
+%% edoc_lib:get_doc_env/4} for further options.
+%%
+%% @see get_doc/3
+%% @see run/3
+%% @see edoc_extract:source/5
+%% @see read/2
+%% @see layout/2
+
+%% INHERIT-OPTIONS: get_doc/3
+%% INHERIT-OPTIONS: edoc_lib:get_doc_env/4
+
+get_doc(File, Opts) ->
+ Env = edoc_lib:get_doc_env(Opts),
+ get_doc(File, Env, Opts).
+
+%% @spec get_doc(File::filename(), Env::edoc_lib:edoc_env(),
+%% Options::proplist()) -> {ModuleName, edoc_module()}
+%% ModuleName = atom()
+%%
+%% @doc Like {@link get_doc/2}, but for a given environment
+%% parameter. `Env' is an environment created by {@link
+%% edoc_lib:get_doc_env/4}.
+
+%% INHERIT-OPTIONS: read_source/2, read_comments/2, edoc_extract:source/5
+%% DEFER-OPTIONS: get_doc/2
+
+get_doc(File, Env, Opts) ->
+ edoc_extract:source(File, Env, Opts).
diff --git a/lib/edoc/src/edoc.hrl b/lib/edoc/src/edoc.hrl
new file mode 100644
index 0000000000..71cc1a52b9
--- /dev/null
+++ b/lib/edoc/src/edoc.hrl
@@ -0,0 +1,100 @@
+%% =====================================================================
+%% Header file for EDoc
+%%
+%% Copyright (C) 2001-2004 Richard Carlsson
+%%
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% Author contact: [email protected]
+%% =====================================================================
+
+%% Note: Documentation in this file is included by edoc_extract.erl
+
+-define(APPLICATION, edoc).
+-define(INFO_FILE, "edoc-info").
+-define(PACKAGE_FILE, "package.edoc").
+-define(OVERVIEW_FILE, "overview.edoc").
+-define(PACKAGE_SUMMARY, "package-summary").
+-define(DEFAULT_SOURCE_SUFFIX, ".erl").
+-define(DEFAULT_FILE_SUFFIX, ".html").
+-define(DEFAULT_DOCLET, edoc_doclet).
+-define(DEFAULT_LAYOUT, edoc_layout).
+-define(APP_DEFAULT, "http://www.erlang.org/edoc/doc").
+-define(CURRENT_DIR, ".").
+-define(SOURCE_DIR, "src").
+-define(EBIN_DIR, "ebin").
+-define(EDOC_DIR, "doc").
+
+-include("edoc_doclet.hrl").
+
+%% Module information
+
+%% @type module() = #module{name = [] | atom(),
+%% parameters = none | [atom()],
+%% functions = ordset(function_name()),
+%% exports = ordset(function_name()),
+%% attributes = ordset({atom(), term()}),
+%% records = [{atom(), [{atom(), term()}]}]}
+%% ordset(T) = sets:ordset(T)
+%% function_name(T) = {atom(), integer()}
+
+-record(module, {name = [],
+ parameters = none,
+ functions = [],
+ exports = [],
+ attributes = [],
+ records = []
+ }).
+
+%% Environment for generating documentation data
+
+-record(env, {module = [],
+ package = [],
+ root = "",
+ file_suffix,
+ package_summary,
+ apps,
+ modules,
+ packages,
+ app_default,
+ macros = [],
+ includes = []
+ }).
+
+%% Simplified comment data
+
+%% @type comment() = #comment{line = integer(),
+%% text = string()}
+
+-record(comment, {line = 0, text}).
+
+%% Module Entries (one per function, plus module header and footer)
+
+%% @type entry() = #entry{name = atom(),
+%% args = [string()],
+%% line = integer(),
+%% export = bool(),
+%% data = term()}
+
+-record(entry, {name, args = [], line = 0, export, data}).
+
+%% Generic tag information
+
+%% @type tag() = #tag{name = atom(),
+%% line = integer(),
+%% data = term()}
+
+-record(tag, {name, line = 0, data}).
diff --git a/lib/edoc/src/edoc_data.erl b/lib/edoc/src/edoc_data.erl
new file mode 100644
index 0000000000..124f8eb9a1
--- /dev/null
+++ b/lib/edoc/src/edoc_data.erl
@@ -0,0 +1,545 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @private
+%% @copyright 2003 Richard Carlsson
+%% @author Richard Carlsson <[email protected]>
+%% @see edoc
+%% @end
+%% =====================================================================
+
+%% @doc Building the EDoc external data structure. See the file
+%% <a href="../priv/edoc.dtd">`edoc.dtd'</a> for details.
+
+-module(edoc_data).
+
+-export([module/4, package/4, overview/4, type/2]).
+
+-include("edoc.hrl").
+
+%% TODO: report multiple definitions of the same type in the same module.
+%% TODO: check that variables in @equiv are found in the signature
+%% TODO: copy types from target (if missing) when using @equiv
+
+%% <!ELEMENT module (args?, description?, author*, copyright?,
+%% version?, since?, deprecated?, see*, reference*,
+%% todo?, behaviour*, callbacks?, typedecls?,
+%% functions)>
+%% <!ATTLIST module
+%% name CDATA #REQUIRED
+%% private NMTOKEN(yes | no) #IMPLIED
+%% hidden NMTOKEN(yes | no) #IMPLIED
+%% root CDATA #IMPLIED>
+%% <!ELEMENT args (arg*)>
+%% <!ELEMENT arg (argName, description?)>
+%% <!ELEMENT argName (#PCDATA)>
+%% <!ELEMENT description (briefDescription, fullDescription?)>
+%% <!ELEMENT briefDescription (#PCDATA)>
+%% <!ELEMENT fullDescription (#PCDATA)>
+%% <!ELEMENT author EMPTY>
+%% <!ATTLIST author
+%% name CDATA #REQUIRED
+%% email CDATA #IMPLIED
+%% website CDATA #IMPLIED>
+%% <!ELEMENT version (#PCDATA)>
+%% <!ELEMENT since (#PCDATA)>
+%% <!ELEMENT copyright (#PCDATA)>
+%% <!ELEMENT deprecated (description)>
+%% <!ELEMENT see (#PCDATA)>
+%% <!ATTLIST see
+%% name CDATA #REQUIRED
+%% href CDATA #IMPLIED>
+%% <!ELEMENT reference (#PCDATA)>
+%% <!ELEMENT todo (#PCDATA)>
+%% <!ELEMENT behaviour (#PCDATA)>
+%% <!ATTLIST behaviour
+%% href CDATA #IMPLIED>
+%% <!ELEMENT callbacks (callback+)>
+%% <!ELEMENT typedecls (typedecl+)>
+%% <!ELEMENT typedecl (typedef, description?)>
+%% <!ELEMENT functions (function+)>
+
+%% NEW-OPTIONS: private, hidden, todo
+%% DEFER-OPTIONS: edoc_extract:source/4
+
+module(Module, Entries, Env, Opts) ->
+ Name = atom_to_list(Module#module.name),
+ HeaderEntry = get_entry(module, Entries),
+ HeaderTags = HeaderEntry#entry.data,
+ AllTags = get_all_tags(Entries),
+ Functions = function_filter(Entries, Opts),
+ Out = {module, ([{name, Name},
+ {root, Env#env.root}]
+ ++ case is_private(HeaderTags) of
+ true -> [{private, "yes"}];
+ false -> []
+ end
+ ++ case is_hidden(HeaderTags) of
+ true -> [{hidden, "yes"}];
+ false -> []
+ end),
+ (module_args(Module#module.parameters)
+ ++ behaviours(Module#module.attributes, Env)
+ ++ get_doc(HeaderTags)
+ ++ authors(HeaderTags)
+ ++ get_version(HeaderTags)
+ ++ get_since(HeaderTags)
+ ++ get_copyright(HeaderTags)
+ ++ get_deprecated(HeaderTags)
+ ++ sees(HeaderTags, Env)
+ ++ references(HeaderTags)
+ ++ todos(HeaderTags, Opts)
+ ++ [{typedecls, types(AllTags, Env)},
+ {functions, functions(Functions, Env, Opts)}
+ | callbacks(Functions, Module, Env, Opts)])
+ },
+ xmerl_lib:expand_element(Out).
+
+get_all_tags(Es) ->
+ lists:flatmap(fun (#entry{data = Ts}) -> Ts end, Es).
+
+is_private(Ts) ->
+ get_tags(private, Ts) =/= [].
+
+description([]) ->
+ [];
+description(Desc) ->
+ ShortDesc = edoc_lib:get_first_sentence(Desc),
+ [{description,
+ [{briefDescription, ShortDesc},
+ {fullDescription, Desc}]}].
+
+module_args(none) ->
+ [];
+module_args(Vs) ->
+ [{args, [{arg, [{argName, [atom_to_list(V)]}]} || V <- Vs]}].
+
+types(Tags, Env) ->
+ [{typedecl, [{label, edoc_types:to_label(Def)}],
+ [edoc_types:to_xml(Def, Env)] ++ description(Doc)}
+ || #tag{name = type, data = {Def, Doc}} <- Tags].
+
+functions(Es, Env, Opts) ->
+ [function(N, As, Export, Ts, Env, Opts)
+ || #entry{name = {_,_}=N, args = As, export = Export, data = Ts}
+ <- Es].
+
+function_filter(Es, Opts) ->
+ Private = proplists:get_bool(private, Opts),
+ Hidden = proplists:get_bool(hidden, Opts),
+ [E || E <- Es, function_filter(E, Private, Hidden)].
+
+%% Note that only entries whose names have the form {_,_} are functions.
+function_filter(#entry{name = {_,_}, export = Export, data = Ts},
+ Private, Hidden) ->
+ ((Export andalso not is_private(Ts)) orelse Private)
+ andalso ((not is_hidden(Ts)) orelse Hidden);
+function_filter(_, _, _) ->
+ false.
+
+is_hidden(Ts) ->
+ get_tags(hidden, Ts) =/= [].
+
+callbacks(Es, Module, Env, Opts) ->
+ case lists:any(fun (#entry{name = {behaviour_info, 1}}) -> true;
+ (_) -> false
+ end,
+ Es) of
+ true ->
+ try (Module#module.name):behaviour_info(callbacks) of
+ Fs ->
+ Fs1 = [{F,A} || {F,A} <- Fs, is_atom(F), is_integer(A)],
+ if Fs1 =:= [] ->
+ [];
+ true ->
+ [{callbacks,
+ [callback(F, Env, Opts) || F <- Fs1]}]
+ end
+ catch
+ _:_ -> []
+ end;
+ false -> []
+ end.
+
+%% <!ELEMENT callback EMPTY>
+%% <!ATTLIST callback
+%% name CDATA #REQUIRED
+%% arity CDATA #REQUIRED>
+
+callback({N, A}, _Env, _Opts) ->
+ {callback, [{name, atom_to_list(N)},
+ {arity, integer_to_list(A)}],
+ []}.
+
+%% <!ELEMENT function (args, typespec?, returns?, throws?, equiv?,
+%% description?, since?, deprecated?, see*, todo?)>
+%% <!ATTLIST function
+%% name CDATA #REQUIRED
+%% arity CDATA #REQUIRED
+%% exported NMTOKEN(yes | no) #REQUIRED
+%% label CDATA #IMPLIED>
+%% <!ELEMENT args (arg*)>
+%% <!ELEMENT arg (argName, description?)>
+%% <!ELEMENT argName (#PCDATA)>
+%% <!ELEMENT returns (description)>
+%% <!ELEMENT throws (type, localdef*)>
+%% <!ELEMENT equiv (expr, see?)>
+%% <!ELEMENT expr (#PCDATA)>
+
+function({N, A}, As, Export, Ts, Env, Opts) ->
+ {Args, Ret, Spec} = signature(Ts, As, Env),
+ {function, [{name, atom_to_list(N)},
+ {arity, integer_to_list(A)},
+ {exported, case Export of
+ true -> "yes";
+ false -> "no"
+ end},
+ {label, edoc_refs:to_label(edoc_refs:function(N, A))}],
+ [{args, [{arg, [{argName, [atom_to_list(A)]}] ++ description(D)}
+ || {A, D} <- Args]}]
+ ++ Spec
+ ++ case Ret of
+ [] -> [];
+ _ -> [{returns, description(Ret)}]
+ end
+ ++ get_throws(Ts, Env)
+ ++ get_equiv(Ts, Env)
+ ++ get_doc(Ts)
+ ++ get_since(Ts)
+ ++ get_deprecated(Ts, N, A, Env)
+ ++ sees(Ts, Env)
+ ++ todos(Ts, Opts)
+ }.
+
+get_throws(Ts, Env) ->
+ case get_tags(throws, Ts) of
+ [Throws] ->
+ Type = Throws#tag.data,
+ [edoc_types:to_xml(Type, Env)];
+ [] ->
+ []
+ end.
+
+get_equiv(Ts, Env) ->
+ case get_tags(equiv, Ts) of
+ [Equiv] ->
+ Expr = Equiv#tag.data,
+ See = case get_expr_ref(Equiv#tag.data) of
+ none -> [];
+ Ref ->
+ [see(Ref, [edoc_refs:to_string(Ref)], Env)]
+ end,
+ [{equiv, [{expr, [erl_prettypr:format(Expr)]} | See]}];
+ [] ->
+ []
+ end.
+
+get_doc(Ts) ->
+ case get_tags(doc, Ts) of
+ [T] ->
+ description(T#tag.data);
+ [] ->
+ []
+ end.
+
+get_copyright(Ts) ->
+ get_pcdata_tag(copyright, Ts).
+
+get_version(Ts) ->
+ get_pcdata_tag(version, Ts).
+
+get_since(Ts) ->
+ get_pcdata_tag(since, Ts).
+
+get_pcdata_tag(Tag, Ts) ->
+ case get_tags(Tag, Ts) of
+ [T] ->
+ [{Tag, [T#tag.data]}];
+ [] ->
+ []
+ end.
+
+%% Deprecation declarations for xref:
+%%
+%% -deprecated(Info).
+%% Info = Spec | [Spec]
+%% Spec = module | {F,A} | {F,A,Details}}
+%% Details = next_version | next_major_release | eventually
+%% (EXTENSION: | string() | {M1,F1,A1}}
+%% TODO: use info from '-deprecated(...)' (xref-)declarations.
+
+get_deprecated(Ts) ->
+ case get_tags(deprecated, Ts) of
+ [T] ->
+ [{deprecated, description(T#tag.data)}];
+ [] ->
+ []
+ end.
+
+get_deprecated(Ts, F, A, Env) ->
+ case get_deprecated(Ts) of
+ [] ->
+ M = Env#env.module,
+ case otp_internal:obsolete(M, F, A) of
+ {Tag, Text} when Tag =:= deprecated; Tag =:= removed ->
+ deprecated([Text]);
+ {Tag, Repl, _Rel} when Tag =:= deprecated; Tag =:= removed ->
+ deprecated(Repl, Env);
+ _ ->
+ []
+ end;
+ Es ->
+ Es
+ end.
+
+deprecated(Repl, Env) ->
+ {Text, Ref} = replacement_function(Env#env.module, Repl),
+ Desc = ["Use ", {a, href(Ref, Env), [{code, [Text]}]}, " instead."],
+ deprecated(Desc).
+
+deprecated(Desc) ->
+ [{deprecated, description(Desc)}].
+
+replacement_function(M0, {M,F,A}) when is_list(A) ->
+ %% refer to the largest listed arity - the most general version
+ replacement_function(M0, {M,F,lists:last(lists:sort(A))});
+replacement_function(M, {M,F,A}) ->
+ {io_lib:fwrite("~w/~w", [F, A]), edoc_refs:function(F, A)};
+replacement_function(_, {M,F,A}) ->
+ {io_lib:fwrite("~w:~w/~w", [M, F, A]), edoc_refs:function(M, F, A)}.
+
+get_expr_ref(Expr) ->
+ case catch {ok, erl_syntax_lib:analyze_application(Expr)} of
+ {ok, {F, A}} when is_atom(F), is_integer(A) ->
+ edoc_refs:function(F, A);
+ {ok, {M, {F, A}}} when is_atom(M), is_atom(F), is_integer(A) ->
+ edoc_refs:function(M, F, A);
+ _ ->
+ none
+ end.
+
+authors(Ts) ->
+ [author(Info) || #tag{data = Info} <- get_tags(author, Ts)].
+
+%% <!ATTLIST author
+%% name CDATA #REQUIRED
+%% email CDATA #IMPLIED
+%% website CDATA #IMPLIED>
+
+author({Name, Mail, URI}) ->
+ %% At least one of Name and Mail must be nonempty in the tag.
+ {author, ([{name, if Name =:= "" -> Mail;
+ true -> Name
+ end}]
+ ++ if Mail =:= "" ->
+ case lists:member($@, Name) of
+ true -> [{email, Name}];
+ false -> []
+ end;
+ true -> [{email, Mail}]
+ end
+ ++ if URI =:= "" -> [];
+ true -> [{website, URI}]
+ end), []}.
+
+behaviours(As, Env) ->
+ [{behaviour, href(edoc_refs:module(B), Env), [atom_to_list(B)]}
+ || {behaviour, B} <- As, is_atom(B)].
+
+sees(Tags, Env) ->
+ Ts = get_tags(see, Tags),
+ Rs = lists:keysort(1, [Data || #tag{data = Data} <- Ts]),
+ [see(Ref, XML, Env) || {Ref, XML} <- Rs].
+
+see(Ref, [], Env) ->
+ see(Ref, [edoc_refs:to_string(Ref)], Env);
+see(Ref, XML, Env) ->
+ {see, [{name, edoc_refs:to_string(Ref)}] ++ href(Ref, Env), XML}.
+
+href(Ref, Env) ->
+ [{href, edoc_refs:get_uri(Ref, Env)}]
+ ++ case edoc_refs:is_top(Ref, Env) of
+ true ->
+ [{target, "_top"}];
+ false ->
+ []
+ end.
+
+references(Tags) ->
+ [{reference, XML} || #tag{data = XML} <- get_tags(reference, Tags)].
+
+todos(Tags, Opts) ->
+ case proplists:get_bool(todo, Opts) of
+ true ->
+ [{todo, XML} || #tag{data = XML} <- get_tags('todo', Tags)];
+ false ->
+ []
+ end.
+
+signature(Ts, As, Env) ->
+ case get_tags(spec, Ts) of
+ [T] ->
+ Spec = T#tag.data,
+ R = merge_returns(Spec, Ts),
+ As0 = edoc_types:arg_names(Spec),
+ Ds0 = edoc_types:arg_descs(Spec),
+ %% choose names in spec before names in code
+ P = dict:from_list(params(Ts)),
+ As1 = merge_args(As0, As, Ds0, P),
+ %% check_params(As1, P),
+ Spec1 = edoc_types:set_arg_names(Spec, [A || {A,_} <- As1]),
+ {As1, R, [edoc_types:to_xml(Spec1, Env)]};
+ [] ->
+ S = sets:new(),
+ {[{A, ""} || A <- fix_argnames(As, S, 1)], [], []}
+ end.
+
+params(Ts) ->
+ [T#tag.data || T <- get_tags(param, Ts)].
+
+%% check_params(As, P) ->
+%% case dict:keys(P) -- [N || {N,_} <- As] of
+%% [] -> ok;
+%% Ps -> error %% TODO: report @param declarations with no match
+%% end.
+
+merge_returns(Spec, Ts) ->
+ case get_tags(returns, Ts) of
+ [] ->
+ case edoc_types:range_desc(Spec) of
+ "" -> [];
+ Txt -> [Txt]
+ end;
+ [T] -> T#tag.data
+ end.
+
+%% Names are chosen from the first list (the specification) if possible.
+%% Descriptions specified with @param (in P dict) override descriptions
+%% from the spec (in Ds).
+
+merge_args(As, As1, Ds, P) ->
+ merge_args(As, As1, Ds, [], P, sets:new(), 1).
+
+merge_args(['_' | As], ['_' | As1], [D | Ds], Rs, P, S, N) ->
+ merge_args(As, As1, Ds, Rs, P, S, N, make_name(N, S), D);
+merge_args(['_' | As], [A | As1], [D | Ds], Rs, P, S, N) ->
+ merge_args(As, As1, Ds, Rs, P, S, N, A, D);
+merge_args([A | As], [_ | As1], [D | Ds], Rs, P, S, N) ->
+ merge_args(As, As1, Ds, Rs, P, S, N, A, D);
+merge_args([], [], [], Rs, _P, _S, _N) ->
+ lists:reverse(Rs).
+
+merge_args(As, As1, Ds, Rs, P, S, N, A, D0) ->
+ D = case dict:find(A, P) of
+ {ok, D1} -> D1;
+ error when D0 =:= [] -> []; % no description
+ error -> [D0] % a simple-xml text element
+ end,
+ merge_args(As, As1, Ds, [{A, D} | Rs], P,
+ sets:add_element(A, S), N + 1).
+
+fix_argnames(['_' | As], S, N) ->
+ A = make_name(N, S),
+ [A | fix_argnames(As, sets:add_element(A, S), N + 1)];
+fix_argnames([A | As], S, N) ->
+ [A | fix_argnames(As, sets:add_element(A, S), N + 1)];
+fix_argnames([], _S, _N) ->
+ [].
+
+make_name(N, S) ->
+ make_name(N, S, "X").
+
+make_name(N, S, Base) ->
+ A = list_to_atom(Base ++ integer_to_list(N)),
+ case sets:is_element(A, S) of
+ true ->
+ make_name(N, S, Base ++ "x");
+ false ->
+ A
+ end.
+
+get_entry(Name, [#entry{name = Name} = E | _Es]) -> E;
+get_entry(Name, [_ | Es]) -> get_entry(Name, Es).
+
+get_tags(Tag, [#tag{name = Tag} = T | Ts]) -> [T | get_tags(Tag, Ts)];
+get_tags(Tag, [_ | Ts]) -> get_tags(Tag, Ts);
+get_tags(_, []) -> [].
+
+%% ---------------------------------------------------------------------
+
+type(T, Env) ->
+ xmerl_lib:expand_element({type, [edoc_types:to_xml(T, Env)]}).
+
+%% <!ELEMENT package (description?, author*, copyright?, version?,
+%% since?, deprecated?, see*, reference*, todo?,
+%% modules)>
+%% <!ATTLIST package
+%% name CDATA #REQUIRED
+%% root CDATA #IMPLIED>
+%% <!ELEMENT modules (module+)>
+
+package(Package, Tags, Env, Opts) ->
+ Env1 = Env#env{package = Package,
+ root = edoc_refs:relative_package_path('', Package)},
+ xmerl_lib:expand_element(package_1(Package, Tags, Env1, Opts)).
+
+package_1(Package, Tags, Env, Opts) ->
+ {package, [{root, Env#env.root}],
+ ([{packageName, [atom_to_list(Package)]}]
+ ++ get_doc(Tags)
+ ++ authors(Tags)
+ ++ get_copyright(Tags)
+ ++ get_version(Tags)
+ ++ get_since(Tags)
+ ++ get_deprecated(Tags)
+ ++ sees(Tags, Env)
+ ++ references(Tags)
+ ++ todos(Tags, Opts))
+ }.
+
+%% <!ELEMENT overview (title, description?, author*, copyright?, version?,
+%% since?, see*, reference*, todo?, packages, modules)>
+%% <!ATTLIST overview
+%% root CDATA #IMPLIED>
+%% <!ELEMENT title (#PCDATA)>
+
+overview(Title, Tags, Env, Opts) ->
+ Env1 = Env#env{package = '',
+ root = ""},
+ xmerl_lib:expand_element(overview_1(Title, Tags, Env1, Opts)).
+
+overview_1(Title, Tags, Env, Opts) ->
+ {overview, [{root, Env#env.root}],
+ ([{title, [get_title(Tags, Title)]}]
+ ++ get_doc(Tags)
+ ++ authors(Tags)
+ ++ get_copyright(Tags)
+ ++ get_version(Tags)
+ ++ get_since(Tags)
+ ++ sees(Tags, Env)
+ ++ references(Tags)
+ ++ todos(Tags, Opts))
+ }.
+
+get_title(Ts, Default) ->
+ case get_tags(title, Ts) of
+ [T] ->
+ T#tag.data;
+ [] ->
+ Default
+ end.
diff --git a/lib/edoc/src/edoc_doclet.erl b/lib/edoc/src/edoc_doclet.erl
new file mode 100644
index 0000000000..f1d876d593
--- /dev/null
+++ b/lib/edoc/src/edoc_doclet.erl
@@ -0,0 +1,521 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @copyright 2003-2006 Richard Carlsson
+%% @author Richard Carlsson <[email protected]>
+%% @see edoc
+%% @end
+%% =====================================================================
+
+%% @doc Standard doclet module for EDoc.
+
+%% Note that this is written so that it is *not* depending on edoc.hrl!
+
+%% TODO: copy "doc-files" subdirectories, recursively.
+%% TODO: generate summary page of TODO-notes
+%% TODO: generate summary page of deprecated things
+%% TODO: generate decent indexes over modules, methods, records, etc.
+
+-module(edoc_doclet).
+
+-export([run/2]).
+
+-import(edoc_report, [report/2, warning/2]).
+
+%% @headerfile "edoc_doclet.hrl"
+-include("../include/edoc_doclet.hrl").
+
+-define(EDOC_APP, edoc).
+-define(DEFAULT_FILE_SUFFIX, ".html").
+-define(INDEX_FILE, "index.html").
+-define(OVERVIEW_FILE, "overview.edoc").
+-define(PACKAGE_SUMMARY, "package-summary.html").
+-define(OVERVIEW_SUMMARY, "overview-summary.html").
+-define(PACKAGES_FRAME, "packages-frame.html").
+-define(MODULES_FRAME, "modules-frame.html").
+-define(STYLESHEET, "stylesheet.css").
+-define(IMAGE, "erlang.png").
+-define(NL, "\n").
+
+-include("xmerl.hrl").
+
+%% Sources is the list of inputs in the order they were found. Packages
+%% and Modules are sorted lists of atoms without duplicates. (They
+%% usually include the data from the edoc-info file in the target
+%% directory, if it exists.) Note that the "empty package" is never
+%% included in Packages!
+
+%% @spec (Command::doclet_gen() | doclet_toc(), edoc_context()) -> ok
+%% @doc Main doclet entry point. See the file <a
+%% href="../include/edoc_doclet.hrl">`edoc_doclet.hrl'</a> for the data
+%% structures used for passing parameters.
+%%
+%% Also see {@link edoc:layout/2} for layout-related options, and
+%% {@link edoc:get_doc/2} for options related to reading source
+%% files.
+%%
+%% Options:
+%% <dl>
+%% <dt>{@type {file_suffix, string()@}}
+%% </dt>
+%% <dd>Specifies the suffix used for output files. The default value is
+%% `".html"'.
+%% </dd>
+%% <dt>{@type {hidden, bool()@}}
+%% </dt>
+%% <dd>If the value is `true', documentation of hidden modules and
+%% functions will also be included. The default value is `false'.
+%% </dd>
+%% <dt>{@type {overview, edoc:filename()@}}
+%% </dt>
+%% <dd>Specifies the name of the overview-file. By default, this doclet
+%% looks for a file `"overview.edoc"' in the target directory.
+%% </dd>
+%% <dt>{@type {private, bool()@}}
+%% </dt>
+%% <dd>If the value is `true', documentation of private modules and
+%% functions will also be included. The default value is `false'.
+%% </dd>
+%% <dt>{@type {stylesheet, string()@}}
+%% </dt>
+%% <dd>Specifies the URI used for referencing the stylesheet. The
+%% default value is `"stylesheet.css"'. If an empty string is
+%% specified, no stylesheet reference will be generated.
+%% </dd>
+%% <dt>{@type {stylesheet_file, edoc:filename()@}}
+%% </dt>
+%% <dd>Specifies the name of the stylesheet file. By default, this
+%% doclet uses the file `"stylesheet.css"' in the `priv'
+%% subdirectory of the EDoc installation directory. The named file
+%% will be copied to the target directory.
+%% </dd>
+%% <dt>{@type {title, string()@}}
+%% </dt>
+%% <dd>Specifies the title of the overview-page.
+%% </dd>
+%% </dl>
+
+%% INHERIT-OPTIONS: title/2
+%% INHERIT-OPTIONS: sources/5
+%% INHERIT-OPTIONS: overview/4
+%% INHERIT-OPTIONS: copy_stylesheet/2
+%% INHERIT-OPTIONS: stylesheet/1
+
+run(#doclet_gen{}=Cmd, Ctxt) ->
+ gen(Cmd#doclet_gen.sources,
+ Cmd#doclet_gen.app,
+ Cmd#doclet_gen.packages,
+ Cmd#doclet_gen.modules,
+ Cmd#doclet_gen.filemap,
+ Ctxt);
+run(#doclet_toc{}=Cmd, Ctxt) ->
+ toc(Cmd#doclet_toc.paths, Ctxt).
+
+gen(Sources, App, Packages, Modules, FileMap, Ctxt) ->
+ Dir = Ctxt#context.dir,
+ Env = Ctxt#context.env,
+ Options = Ctxt#context.opts,
+ Title = title(App, Options),
+ CSS = stylesheet(Options),
+ {Modules1, Error} = sources(Sources, Dir, Modules, Env, Options),
+ modules_frame(Dir, Modules1, Title, CSS),
+ packages(Packages, Dir, FileMap, Env, Options),
+ packages_frame(Dir, Packages, Title, CSS),
+ overview(Dir, Title, Env, Options),
+ index_file(Dir, length(Packages) > 1, Title),
+ edoc_lib:write_info_file(App, Packages, Modules1, Dir),
+ copy_stylesheet(Dir, Options),
+ copy_image(Dir),
+ %% handle postponed error during processing of source files
+ case Error of
+ true -> exit(error);
+ false -> ok
+ end.
+
+
+%% NEW-OPTIONS: title
+%% DEFER-OPTIONS: run/2
+
+title(App, Options) ->
+ proplists:get_value(title, Options,
+ if App == ?NO_APP ->
+ "Overview";
+ true ->
+ io_lib:fwrite("Application: ~s", [App])
+ end).
+
+
+%% Processing the individual source files.
+
+%% NEW-OPTIONS: file_suffix, private, hidden
+%% INHERIT-OPTIONS: edoc:layout/2
+%% INHERIT-OPTIONS: edoc:get_doc/3
+%% DEFER-OPTIONS: run/2
+
+sources(Sources, Dir, Modules, Env, Options) ->
+ Suffix = proplists:get_value(file_suffix, Options,
+ ?DEFAULT_FILE_SUFFIX),
+ Private = proplists:get_bool(private, Options),
+ Hidden = proplists:get_bool(hidden, Options),
+ {Ms, E} = lists:foldl(fun (Src, {Set, Error}) ->
+ source(Src, Dir, Suffix, Env, Set,
+ Private, Hidden, Error, Options)
+ end,
+ {sets:new(), false}, Sources),
+ {[M || M <- Modules, sets:is_element(M, Ms)], E}.
+
+
+%% Generating documentation for a source file, adding its name to the
+%% set if it was successful. Errors are just flagged at this stage,
+%% allowing all source files to be processed even if some of them fail.
+
+source({M, P, Name, Path}, Dir, Suffix, Env, Set, Private, Hidden,
+ Error, Options) ->
+ File = filename:join(Path, Name),
+ case catch {ok, edoc:get_doc(File, Env, Options)} of
+ {ok, {Module, Doc}} ->
+ check_name(Module, M, P, File),
+ case ((not is_private(Doc)) orelse Private)
+ andalso ((not is_hidden(Doc)) orelse Hidden) of
+ true ->
+ Text = edoc:layout(Doc, Options),
+ Name1 = packages:last(M) ++ Suffix,
+ edoc_lib:write_file(Text, Dir, Name1, P),
+ {sets:add_element(Module, Set), Error};
+ false ->
+ {Set, Error}
+ end;
+ R ->
+ report("skipping source file '~s': ~W.", [File, R, 15]),
+ {Set, true}
+ end.
+
+check_name(M, M0, P0, File) ->
+ P = list_to_atom(packages:strip_last(M)),
+ N = packages:last(M),
+ N0 = packages:last(M0),
+ case N of
+ [$? | _] ->
+ %% A module name of the form '?...' is assumed to be caused
+ %% by the epp_dodger parser when the module declaration has
+ %% the form '-module(?MACRO).'; skip the filename check.
+ ok;
+ _ ->
+ if N =/= N0 ->
+ warning("file '~s' actually contains module '~s'.",
+ [File, M]);
+ true ->
+ ok
+ end
+ end,
+ if P =/= P0 ->
+ warning("file '~s' belongs to package '~s', not '~s'.",
+ [File, P, P0]);
+ true ->
+ ok
+ end.
+
+
+%% Generating the summary files for packages.
+
+%% INHERIT-OPTIONS: read_file/4
+%% INHERIT-OPTIONS: edoc_lib:run_layout/2
+
+packages(Packages, Dir, FileMap, Env, Options) ->
+ lists:foreach(fun (P) ->
+ package(P, Dir, FileMap, Env, Options)
+ end,
+ Packages).
+
+package(P, Dir, FileMap, Env, Opts) ->
+ Tags = case FileMap(P) of
+ "" ->
+ [];
+ File ->
+ read_file(File, package, Env, Opts)
+ end,
+ Data = edoc_data:package(P, Tags, Env, Opts),
+ F = fun (M) ->
+ M:package(Data, Opts)
+ end,
+ Text = edoc_lib:run_layout(F, Opts),
+ edoc_lib:write_file(Text, Dir, ?PACKAGE_SUMMARY, P).
+
+
+%% Creating an index file, with some frames optional.
+%% TODO: get rid of frames, or change doctype to Frameset
+
+index_file(Dir, Packages, Title) ->
+ Frame1 = {frame, [{src,?PACKAGES_FRAME},
+ {name,"packagesFrame"},{title,""}],
+ []},
+ Frame2 = {frame, [{src,?MODULES_FRAME},
+ {name,"modulesFrame"},{title,""}],
+ []},
+ Frame3 = {frame, [{src,?OVERVIEW_SUMMARY},
+ {name,"overviewFrame"},{title,""}],
+ []},
+ Frameset = {frameset, [{cols,"20%,80%"}],
+ case Packages of
+ true ->
+ [?NL,
+ {frameset, [{rows,"30%,70%"}],
+ [?NL, Frame1, ?NL, Frame2, ?NL]}
+ ];
+ false ->
+ [?NL, Frame2, ?NL]
+ end
+ ++ [?NL, Frame3, ?NL,
+ {noframes,
+ [?NL,
+ {h2, ["This page uses frames"]},
+ ?NL,
+ {p, ["Your browser does not accept frames.",
+ ?NL, br,
+ "You should go to the ",
+ {a, [{href, ?OVERVIEW_SUMMARY}],
+ ["non-frame version"]},
+ " instead.", ?NL]},
+ ?NL]},
+ ?NL]},
+ XML = xhtml_1(Title, [], Frameset),
+ Text = xmerl:export_simple([XML], xmerl_html, []),
+ edoc_lib:write_file(Text, Dir, ?INDEX_FILE).
+
+packages_frame(Dir, Ps, Title, CSS) ->
+ Body = [?NL,
+ {h2, [{class, "indextitle"}], ["Packages"]},
+ ?NL,
+ {table, [{width, "100%"}, {border, 0},
+ {summary, "list of packages"}],
+ lists:concat(
+ [[?NL,
+ {tr, [{td, [], [{a, [{href, package_ref(P)},
+ {target,"overviewFrame"},
+ {class, "package"}],
+ [atom_to_list(P)]}]}]}]
+ || P <- Ps])},
+ ?NL],
+ XML = xhtml(Title, CSS, Body),
+ Text = xmerl:export_simple([XML], xmerl_html, []),
+ edoc_lib:write_file(Text, Dir, ?PACKAGES_FRAME).
+
+modules_frame(Dir, Ms, Title, CSS) ->
+ Body = [?NL,
+ {h2, [{class, "indextitle"}], ["Modules"]},
+ ?NL,
+ {table, [{width, "100%"}, {border, 0},
+ {summary, "list of modules"}],
+ lists:concat(
+ [[?NL,
+ {tr, [{td, [],
+ [{a, [{href, module_ref(M)},
+ {target, "overviewFrame"},
+ {class, "module"}],
+ [atom_to_list(M)]}]}]}]
+ || M <- Ms])},
+ ?NL],
+ XML = xhtml(Title, CSS, Body),
+ Text = xmerl:export_simple([XML], xmerl_html, []),
+ edoc_lib:write_file(Text, Dir, ?MODULES_FRAME).
+
+module_ref(M) ->
+ edoc_refs:relative_package_path(M, '') ++ ?DEFAULT_FILE_SUFFIX.
+
+package_ref(P) ->
+ edoc_lib:join_uri(edoc_refs:relative_package_path(P, ''),
+ ?PACKAGE_SUMMARY).
+
+xhtml(Title, CSS, Content) ->
+ xhtml_1(Title, CSS, {body, [{bgcolor, "white"}], Content}).
+
+xhtml_1(Title, CSS, Body) ->
+ {html, [?NL,
+ {head, [?NL, {title, [Title]}, ?NL] ++ CSS},
+ ?NL,
+ Body,
+ ?NL]
+ }.
+
+%% NEW-OPTIONS: overview
+%% INHERIT-OPTIONS: read_file/4
+%% INHERIT-OPTIONS: edoc_lib:run_layout/2
+%% INHERIT-OPTIONS: edoc_extract:file/4
+%% DEFER-OPTIONS: run/2
+
+overview(Dir, Title, Env, Opts) ->
+ File = proplists:get_value(overview, Opts,
+ filename:join(Dir, ?OVERVIEW_FILE)),
+ Tags = read_file(File, overview, Env, Opts),
+ Data = edoc_data:overview(Title, Tags, Env, Opts),
+ F = fun (M) ->
+ M:overview(Data, Opts)
+ end,
+ Text = edoc_lib:run_layout(F, Opts),
+ edoc_lib:write_file(Text, Dir, ?OVERVIEW_SUMMARY).
+
+
+copy_image(Dir) ->
+ case code:priv_dir(?EDOC_APP) of
+ PrivDir when is_list(PrivDir) ->
+ From = filename:join(PrivDir, ?IMAGE),
+ edoc_lib:copy_file(From, filename:join(Dir, ?IMAGE));
+ _ ->
+ report("cannot find default image file.", []),
+ exit(error)
+ end.
+
+%% NEW-OPTIONS: stylesheet_file
+%% DEFER-OPTIONS: run/2
+
+copy_stylesheet(Dir, Options) ->
+ case proplists:get_value(stylesheet, Options) of
+ undefined ->
+ From = case proplists:get_value(stylesheet_file, Options) of
+ File when is_list(File) ->
+ File;
+ _ ->
+ case code:priv_dir(?EDOC_APP) of
+ PrivDir when is_list(PrivDir) ->
+ filename:join(PrivDir, ?STYLESHEET);
+ _ ->
+ report("cannot find default "
+ "stylesheet file.", []),
+ exit(error)
+ end
+ end,
+ edoc_lib:copy_file(From, filename:join(Dir, ?STYLESHEET));
+ _ ->
+ ok
+ end.
+
+%% NEW-OPTIONS: stylesheet
+%% DEFER-OPTIONS: run/2
+
+stylesheet(Options) ->
+ case proplists:get_value(stylesheet, Options) of
+ "" ->
+ [];
+ S ->
+ Ref = case S of
+ undefined ->
+ ?STYLESHEET;
+ "" ->
+ ""; % no stylesheet
+ S when is_list(S) ->
+ S;
+ _ ->
+ report("bad value for option 'stylesheet'.",
+ []),
+ exit(error)
+ end,
+ [{link, [{rel, "stylesheet"},
+ {type, "text/css"},
+ {href, Ref},
+ {title, "EDoc"}], []},
+ ?NL]
+ end.
+
+is_private(E) ->
+ case get_attrval(private, E) of
+ "yes" -> true;
+ _ -> false
+ end.
+
+is_hidden(E) ->
+ case get_attrval(hidden, E) of
+ "yes" -> true;
+ _ -> false
+ end.
+
+get_attrval(Name, #xmlElement{attributes = As}) ->
+ case get_attr(Name, As) of
+ [#xmlAttribute{value = V}] ->
+ V;
+ [] -> ""
+ end.
+
+get_attr(Name, [#xmlAttribute{name = Name} = A | As]) ->
+ [A | get_attr(Name, As)];
+get_attr(Name, [_ | As]) ->
+ get_attr(Name, As);
+get_attr(_, []) ->
+ [].
+
+%% Read external source file. Fails quietly, returning empty tag list.
+
+%% INHERIT-OPTIONS: edoc_extract:file/4
+
+read_file(File, Context, Env, Opts) ->
+ case edoc_extract:file(File, Context, Env, Opts) of
+ {ok, Tags} ->
+ Tags;
+ {error, _} ->
+ []
+ end.
+
+
+%% TODO: FIXME: meta-level index generation
+
+%% Creates a Table of Content from a list of Paths (ie paths to applications)
+%% and an overview file.
+
+-define(EDOC_DIR, "doc").
+-define(INDEX_DIR, "doc/index").
+-define(CURRENT_DIR, ".").
+
+toc(Paths, Ctxt) ->
+ Opts = Ctxt#context.opts,
+ Dir = Ctxt#context.dir,
+ Env = Ctxt#context.env,
+ app_index_file(Paths, Dir, Env, Opts).
+
+%% TODO: FIXME: it's unclear how much of this is working at all
+
+%% NEW-OPTIONS: title
+%% INHERIT-OPTIONS: overview/4
+
+app_index_file(Paths, Dir, Env, Options) ->
+ Title = proplists:get_value(title, Options,"Overview"),
+% Priv = proplists:get_bool(private, Options),
+ CSS = stylesheet(Options),
+ Apps1 = [{filename:dirname(A),filename:basename(A)} || A <- Paths],
+ index_file(Dir, false, Title),
+ application_frame(Dir, Apps1, Title, CSS),
+ modules_frame(Dir, [], Title, CSS),
+ overview(Dir, Title, Env, Options),
+% edoc_lib:write_info_file(Prod, [], Modules1, Dir),
+ copy_stylesheet(Dir, Options).
+
+application_frame(Dir, Apps, Title, CSS) ->
+ Body = [?NL,
+ {h2, ["Applications"]},
+ ?NL,
+ {table, [{width, "100%"}, {border, 0}],
+ lists:concat(
+ [[{tr, [{td, [], [{a, [{href,app_ref(Path,App)},
+ {target,"_top"}],
+ [App]}]}]}]
+ || {Path,App} <- Apps])},
+ ?NL],
+ XML = xhtml(Title, CSS, Body),
+ Text = xmerl:export_simple([XML], xmerl_html, []),
+ edoc_lib:write_file(Text, Dir, ?MODULES_FRAME).
+
+app_ref(Path,M) ->
+ filename:join([Path,M,?EDOC_DIR,?INDEX_FILE]).
diff --git a/lib/edoc/src/edoc_extract.erl b/lib/edoc/src/edoc_extract.erl
new file mode 100644
index 0000000000..ea2755f7aa
--- /dev/null
+++ b/lib/edoc/src/edoc_extract.erl
@@ -0,0 +1,584 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @copyright 2001-2003 Richard Carlsson
+%% @author Richard Carlsson <[email protected]>
+%% @see edoc
+%% @end
+%% =====================================================================
+
+%% @doc EDoc documentation extraction.
+
+-module(edoc_extract).
+
+-export([source/3, source/4, source/5, header/3, header/4, header/5,
+ file/4, text/4]).
+
+-import(edoc_report, [report/3, warning/3]).
+
+%% %% @headerfile "edoc.hrl" (disabled until it can be made private)
+-include("edoc.hrl").
+
+%% @type filename() = file:filename()
+
+%% @spec source(File::filename(), Env::edoc_env(), Options::proplist())
+%% -> {ModuleName, edoc_module()}
+%% ModuleName = atom()
+%% proplist() = [term()]
+%%
+%% @doc Like {@link source/5}, but reads the syntax tree and the
+%% comments from the specified file.
+%%
+%% @see edoc:read_comments/2
+%% @see edoc:read_source/2
+%% @see source/4
+
+source(File, Env, Opts) ->
+ Forms = edoc:read_source(File, Opts),
+ Comments = edoc:read_comments(File, Opts),
+ source(Forms, Comments, File, Env, Opts).
+
+%% @spec source(Forms, Comments::[comment()], File::filename(),
+%% Env::edoc_env(), Options::proplist()) ->
+%% {ModuleName, edoc_module()}
+%%
+%% Forms = syntaxTree() | [syntaxTree()]
+%% comment() = {Line, Column, Indentation, Text}
+%% Line = integer()
+%% Column = integer()
+%% Indentation = integer()
+%% Text = [string()]
+%% ModuleName = atom()
+%%
+%% @doc Like {@link source/4}, but first inserts the given comments in
+%% the syntax trees. The syntax trees must contain valid position
+%% information. (Cf. {@link edoc:read_comments/2}.)
+%%
+%% @see edoc:read_comments/2
+%% @see edoc:read_source/2
+%% @see source/3
+%% @see source/4
+%% @see //syntax_tools/erl_recomment
+
+source(Forms, Comments, File, Env, Opts) when is_list(Forms) ->
+ Forms1 = erl_syntax:form_list(Forms),
+ source(Forms1, Comments, File, Env, Opts);
+source(Forms, Comments, File, Env, Opts) ->
+ Tree = erl_recomment:quick_recomment_forms(Forms, Comments),
+ source(Tree, File, Env, Opts).
+
+%% @spec source(Forms, File::filename(), Env::edoc_env(),
+%% Options::proplist()) ->
+%% {ModuleName, edoc_module()}
+%%
+%% Forms = syntaxTree() | [syntaxTree()]
+%% ModuleName = atom()
+%% edoc_module() = edoc:edoc_module()
+%% @type edoc_env() = edoc_lib:edoc_env()
+%%
+%% @doc Extracts EDoc documentation from commented source code syntax
+%% trees. The given `Forms' must be a single syntax tree of
+%% type `form_list', or a list of syntax trees representing
+%% "program forms" (cf. {@link edoc:read_source/2}.
+%% `Env' is an environment created by {@link
+%% edoc_lib:get_doc_env/4}. The `File' argument is used for
+%% error reporting and output file name generation only.
+%%
+%% See {@link edoc:get_doc/2} for descriptions of the `def',
+%% `hidden', `private', and `todo' options.
+%%
+%% @see edoc:read_comments/2
+%% @see edoc:read_source/2
+%% @see source/5
+%% @see //syntax_tools/erl_recomment
+
+%% Note that the actual module name found in the source file will be
+%% used for generating the documentation, creating relative links, etc.
+
+%% INHERIT-OPTIONS: add_macro_defs/3
+%% INHERIT-OPTIONS: edoc_data:module/4
+
+source(Forms, File, Env, Opts) when is_list(Forms) ->
+ source(erl_syntax:form_list(Forms), File, Env, Opts);
+source(Tree, File0, Env, Opts) ->
+ Forms = preprocess_forms(Tree),
+ File = edoc_lib:filename(File0),
+ Module = get_module_info(Tree, File),
+ {Header, Footer, Entries} = collect(Forms, Module),
+ Name = Module#module.name,
+ Package = list_to_atom(packages:strip_last(Name)),
+ Env1 = Env#env{module = Name,
+ package = Package,
+ root = edoc_refs:relative_package_path('', Package)},
+ Env2 = add_macro_defs(module_macros(Env1), Opts, Env1),
+ Entries1 = get_tags([Header, Footer | Entries], Env2, File),
+ Data = edoc_data:module(Module, Entries1, Env2, Opts),
+ {Name, Data}.
+
+
+%% @spec header(File::filename(), Env::edoc_env(), Options::proplist())
+%% -> {ok, Tags} | {error, Reason}
+%% Tags = [term()]
+%% Reason = term()
+%%
+%% @doc Similar to {@link header/5}, but reads the syntax tree and the
+%% comments from the specified file.
+%%
+%% @see edoc:read_comments/2
+%% @see edoc:read_source/2
+%% @see header/4
+
+header(File, Env, Opts) ->
+ Forms = edoc:read_source(File),
+ Comments = edoc:read_comments(File),
+ header(Forms, Comments, File, Env, Opts).
+
+%% @spec header(Forms, Comments::[comment()], File::filename(),
+%% Env::edoc_env(), Options::proplist()) ->
+%% {ok, Tags} | {error, Reason}
+%% Forms = syntaxTree() | [syntaxTree()]
+%% Tags = [term()]
+%% Reason = term()
+%%
+%% @doc Similar to {@link header/4}, but first inserts the given
+%% comments in the syntax trees. The syntax trees must contain valid
+%% position information. (Cf. {@link edoc:read_comments/2}.)
+%%
+%% @see header/3
+%% @see header/4
+%% @see //syntax_tools/erl_recomment
+
+header(Forms, Comments, File, Env, Opts) when is_list(Forms) ->
+ Forms1 = erl_syntax:form_list(Forms),
+ header(Forms1, Comments, File, Env, Opts);
+header(Forms, Comments, File, Env, Opts) ->
+ Tree = erl_recomment:quick_recomment_forms(Forms, Comments),
+ header(Tree, File, Env, Opts).
+
+%% @spec header(Forms, File::filename(), Env::edoc_env(),
+%% Options::proplist()) ->
+%% {ok, Tags} | {error, Reason}
+%% Forms = syntaxTree() | [syntaxTree()]
+%% Tags = [term()]
+%% Reason = term()
+%%
+%% @doc Extracts EDoc documentation from commented header file syntax
+%% trees. Similar to {@link source/5}, but ignores any documentation
+%% that occurs before a module declaration or a function definition.
+%% (Warning messages are printed if content may be ignored.) `Env' is
+%% assumed to already be set up with a suitable module context.
+%%
+%% @see header/5
+%% @see //syntax_tools/erl_recomment
+
+header(Forms, File, Env, Opts) when is_list(Forms) ->
+ header(erl_syntax:form_list(Forms), File, Env, Opts);
+header(Tree, File0, Env, _Opts) ->
+ Forms = preprocess_forms(Tree),
+ File = edoc_lib:filename(File0),
+ Module = #module{name = Env#env.module}, % a dummy module record
+ %% We take only "footer" tags, i.e., any kind of definition will
+ %% kill all the information above it up to that point. Then we call
+ %% this the 'header' to make error reports make better sense.
+ {Header, Footer, Entries} = collect(Forms, Module),
+ if Header#entry.data /= [] ->
+ warning(File, "documentation before module declaration is ignored by @headerfile", []);
+ true -> ok
+ end,
+ if Entries /= [] ->
+ warning(File, "documentation before function definitions is ignored by @headerfile", []);
+ true -> ok
+ end,
+ [Entry] = get_tags([Footer#entry{name = header}], Env, File),
+ Entry#entry.data.
+
+%% NEW-OPTIONS: def
+%% DEFER-OPTIONS: source/4
+
+add_macro_defs(Defs0, Opts, Env) ->
+ Defs = proplists:append_values(def, Opts),
+ edoc_macros:check_defs(Defs),
+ Env#env{macros = Defs ++ Defs0 ++ Env#env.macros}.
+
+
+%% @spec file(File::filename(), Context, Env::edoc_env(),
+%% Options::proplist()) -> {ok, Tags} | {error, Reason}
+%% Context = overview | package
+%% Tags = [term()]
+%% Reason = term()
+%%
+%% @doc Reads a text file and returns the list of tags in the file. Any
+%% lines of text before the first tag are ignored. `Env' is an
+%% environment created by {@link edoc_lib:get_doc_env/4}. Upon error,
+%% `Reason' is an atom returned from the call to {@link
+%% //kernel/file:read_file/1}.
+%%
+%% See {@link text/4} for options.
+
+%% INHERIT-OPTIONS: text/4
+
+file(File, Context, Env, Opts) ->
+ case file:read_file(File) of
+ {ok, Bin} ->
+ {ok, text(binary_to_list(Bin), Context, Env, Opts, File)};
+ {error, _R} = Error ->
+ Error
+ end.
+
+
+%% @spec (Text::string(), Context, Env::edoc_env(),
+%% Options::proplist()) -> Tags
+%% Context = overview | package
+%% Tags = [term()]
+%%
+%% @doc Returns the list of tags in the text. Any lines of text before
+%% the first tag are ignored. `Env' is an environment created by {@link
+%% edoc_lib:get_doc_env/4}.
+%%
+%% See {@link source/4} for a description of the `def' option.
+
+%% INHERIT-OPTIONS: add_macro_defs/3
+%% DEFER-OPTIONS: source/4
+
+text(Text, Context, Env, Opts) ->
+ text(Text, Context, Env, Opts, "").
+
+text(Text, Context, Env, Opts, Where) ->
+ Env1 = add_macro_defs(file_macros(Context, Env), Opts, Env),
+ Cs = edoc_lib:lines(Text),
+ Ts0 = edoc_tags:scan_lines(Cs, 1),
+ Tags = sets:from_list(edoc_tags:tag_names()),
+ Ts1 = edoc_tags:filter_tags(Ts0, Tags, Where),
+ Single = sets:from_list(edoc_tags:tags(single)),
+ Allow = sets:from_list(edoc_tags:tags(Context)),
+ case edoc_tags:check_tags(Ts1, Allow, Single, Where) of
+ true ->
+ exit(error);
+ false ->
+ Ts2 = edoc_macros:expand_tags(Ts1, Env1, Where),
+ How = dict:from_list(edoc_tags:tag_parsers()),
+ edoc_tags:parse_tags(Ts2, How, Env1, Where)
+ end.
+
+
+%% @spec (Forms::[syntaxTree()], File::filename()) -> moduleInfo()
+%% @doc Initialises a module-info record with data about the module
+%% represented by the list of forms. Exports are guaranteed to exist in
+%% the set of defined names.
+
+get_module_info(Forms, File) ->
+ L = case catch {ok, erl_syntax_lib:analyze_forms(Forms)} of
+ {ok, L1} ->
+ L1;
+ syntax_error ->
+ report(File, "syntax error in input.", []),
+ exit(error);
+ {'EXIT', R} ->
+ exit(R);
+ R ->
+ throw(R)
+ end,
+ {Name, Vars} = case lists:keyfind(module, 1, L) of
+ {module, N} when is_atom(N) ->
+ {N, none};
+ {module, {N, _Vs} = NVs} when is_atom(N) ->
+ NVs;
+ _ ->
+ report(File, "module name missing.", []),
+ exit(error)
+ end,
+ Functions = ordsets:from_list(get_list_keyval(functions, L)),
+ Exports = ordsets:from_list(get_list_keyval(exports, L)),
+ Attributes = ordsets:from_list(get_list_keyval(attributes, L)),
+ Records = get_list_keyval(records, L),
+ #module{name = Name,
+ parameters = Vars,
+ functions = Functions,
+ exports = ordsets:intersection(Exports, Functions),
+ attributes = Attributes,
+ records = Records}.
+
+get_list_keyval(Key, L) ->
+ case lists:keyfind(Key, 1, L) of
+ {Key, As} ->
+ ordsets:from_list(As);
+ _ ->
+ []
+ end.
+
+%% @spec (Forms::[syntaxTree()]) -> [syntaxTree()]
+%% @doc Preprocessing: copies any precomments on forms to standalone
+%% comments, and removes "invisible" forms from the list.
+
+preprocess_forms(Tree) ->
+ preprocess_forms_1(erl_syntax:form_list_elements(
+ erl_syntax:flatten_form_list(Tree))).
+
+preprocess_forms_1([F | Fs]) ->
+ case erl_syntax:get_precomments(F) of
+ [] ->
+ preprocess_forms_2(F, Fs);
+ Cs ->
+ Cs ++ preprocess_forms_2(F, Fs)
+ end;
+preprocess_forms_1([]) ->
+ [].
+
+preprocess_forms_2(F, Fs) ->
+ case erl_syntax_lib:analyze_form(F) of
+ comment ->
+ [F | preprocess_forms_1(Fs)];
+ {function, _} ->
+ [F | preprocess_forms_1(Fs)];
+ {rule, _} ->
+ [F | preprocess_forms_1(Fs)];
+ {attribute, {module, _}} ->
+ [F | preprocess_forms_1(Fs)];
+ text ->
+ [F | preprocess_forms_1(Fs)];
+ _ ->
+ preprocess_forms_1(Fs)
+ end.
+
+%% This collects the data for the header and the functions of the
+%% module. Note that the list of forms is assumed to have been
+%% preprocessed first, so that all "invisible" forms are removed, and
+%% the only interesting comments are those that are standalone comments
+%% in the list.
+
+collect(Fs, Mod) ->
+ collect(Fs, [], [], undefined, Mod).
+
+collect([F | Fs], Cs, As, Header, Mod) ->
+ case erl_syntax_lib:analyze_form(F) of
+ comment ->
+ collect(Fs, [F | Cs], As, Header, Mod);
+ {function, Name} ->
+ L = erl_syntax:get_pos(F),
+ Export = ordsets:is_element(Name, Mod#module.exports),
+ Args = parameters(erl_syntax:function_clauses(F)),
+ collect(Fs, [], [#entry{name = Name, args = Args, line = L,
+ export = Export,
+ data = comment_text(Cs)} | As],
+ Header, Mod);
+ {rule, Name} ->
+ L = erl_syntax:get_pos(F),
+ Export = ordsets:is_element(Name, Mod#module.exports),
+ Args = parameters(erl_syntax:rule_clauses(F)),
+ collect(Fs, [], [#entry{name = Name, args = Args, line = L,
+ export = Export,
+ data = comment_text(Cs)} | As],
+ Header, Mod);
+ {attribute, {module, _}} when Header =:= undefined ->
+ L = erl_syntax:get_pos(F),
+ collect(Fs, [], As, #entry{name = module, line = L,
+ data = comment_text(Cs)},
+ Mod);
+ _ ->
+ %% Drop current seen comments.
+ collect(Fs, [], As, Header, Mod)
+ end;
+collect([], Cs, As, Header, _Mod) ->
+ Footer = #entry{name = footer, data = comment_text(Cs)},
+ As1 = lists:reverse(As),
+ if Header =:= undefined ->
+ {#entry{name = module, data = []}, Footer, As1};
+ true ->
+ {Header, Footer, As1}
+ end.
+
+%% Returns a list of simplified comment information (position and text)
+%% for a list of abstract comments. The order of elements is reversed.
+
+comment_text(Cs) ->
+ comment_text(Cs, []).
+
+comment_text([C | Cs], Ss) ->
+ L = erl_syntax:get_pos(C),
+ comment_text(Cs, [#comment{line = L,
+ text = [remove_percent_chars(S)
+ || S <- erl_syntax:comment_text(C)]}
+ | Ss]);
+comment_text([], Ss) ->
+ Ss.
+
+%% @spec (string()) -> string()
+%%
+%% @doc Replaces leading `%' characters by spaces. For example, `"%%%
+%% foo" -> "\s\s\s foo"', but `"% % foo" -> "\s % foo"', since the
+%% second `%' is preceded by whitespace.
+
+remove_percent_chars([$% | Cs]) -> [$\s | remove_percent_chars(Cs)];
+remove_percent_chars(Cs) -> Cs.
+
+%% Extracting possible parameter names from Erlang clause patterns. The
+%% atom '_' is used when no name can be found. (Better names are made up
+%% later, when we also may have typespecs available; see edoc_data.)
+
+parameters(Clauses) ->
+ select_names([find_names(Ps) || Ps <- patterns(Clauses)]).
+
+patterns(Cs) ->
+ edoc_lib:transpose([erl_syntax:clause_patterns(C) || C <- Cs]).
+
+find_names(Ps) ->
+ find_names(Ps, []).
+
+find_names([P | Ps], Ns) ->
+ case erl_syntax:type(P) of
+ variable ->
+ find_names(Ps, [tidy_name(erl_syntax:variable_name(P)) | Ns]);
+ match_expr ->
+ %% Right-hand side gets priority over left-hand side!
+ %% Note that the list is reversed afterwards.
+ P1 = erl_syntax:match_expr_pattern(P),
+ P2 = erl_syntax:match_expr_body(P),
+ find_names([P1, P2 | Ps], Ns);
+ list ->
+ P1 = erl_syntax:list_tail(P),
+ find_names([P1 | Ps], Ns);
+ record_expr ->
+ A = erl_syntax:record_expr_type(P),
+ N = list_to_atom(capitalize(erl_syntax:atom_name(A))),
+ find_names(Ps, [N | Ns]);
+ infix_expr ->
+ %% this can only be a '++' operation
+ P1 = erl_syntax:infix_expr_right(P),
+ find_names([P1 | Ps], Ns);
+ _ ->
+ find_names(Ps, Ns)
+ end;
+find_names([], Ns) ->
+ lists:reverse(Ns).
+
+select_names(Ls) ->
+ select_names(Ls, [], sets:new()).
+
+select_names([Ns | Ls], As, S) ->
+ A = select_name(Ns, S),
+ select_names(Ls, [A | As], sets:add_element(A, S));
+select_names([], As, _) ->
+ lists:reverse(As).
+
+select_name([A | Ns], S) ->
+ case sets:is_element(A, S) of
+ true ->
+ select_name(Ns, S);
+ false ->
+ A
+ end;
+select_name([], _S) ->
+ '_'.
+
+%% Strip leading underscore characters from parameter names. If the
+%% result does not begin with an uppercase character, we add a single
+%% leading underscore. If the result would be empty, the atom '_' is
+%% returned.
+
+tidy_name(A) ->
+ case atom_to_list(A) of
+ [$_ | Cs] ->
+ list_to_atom(tidy_name_1(Cs));
+ _ ->
+ A
+ end.
+
+tidy_name_1([$_ | Cs]) -> tidy_name_1(Cs);
+tidy_name_1([C | _]=Cs) when C >= $A, C =< $Z -> Cs;
+tidy_name_1([C | _]=Cs) when C >= $\300, C =< $\336, C =/= $\327-> Cs;
+tidy_name_1(Cs) -> [$_ | Cs].
+
+%% Change initial character from lowercase to uppercase.
+
+capitalize([C | Cs]) when C >= $a, C =< $z -> [C - 32 | Cs];
+capitalize(Cs) -> Cs.
+
+%% Collects the tags belonging to each entry, checks them, expands
+%% macros and parses the content.
+
+%% %This is commented out until it can be made private
+%% %@type tags() = #tags{names = set(atom()),
+%% % single = set(atom()),
+%% % module = set(atom()),
+%% % footer = set(atom()),
+%% % function = set(atom())}
+%% % set(T) = sets:set(T)
+
+-record(tags, {names,single,module,function,footer}).
+
+get_tags(Es, Env, File) ->
+ %% Cache this stuff for quick lookups.
+ Tags = #tags{names = sets:from_list(edoc_tags:tag_names()),
+ single = sets:from_list(edoc_tags:tags(single)),
+ module = sets:from_list(edoc_tags:tags(module)),
+ footer = sets:from_list(edoc_tags:tags(footer)),
+ function = sets:from_list(edoc_tags:tags(function))},
+ How = dict:from_list(edoc_tags:tag_parsers()),
+ get_tags(Es, Tags, Env, How, File).
+
+get_tags([#entry{name = Name, data = Cs} = E | Es], Tags, Env,
+ How, File) ->
+ Where = {File, Name},
+ Ts0 = scan_tags(Cs),
+ Ts1 = check_tags(Ts0, Tags, Where),
+ Ts2 = edoc_macros:expand_tags(Ts1, Env, Where),
+ Ts = edoc_tags:parse_tags(Ts2, How, Env, Where),
+ [E#entry{data = Ts} | get_tags(Es, Tags, Env, How, File)];
+get_tags([], _, _, _, _) ->
+ [].
+
+%% Scanning a list of separate comments for tags.
+
+scan_tags([#comment{line = L, text = Ss} | Es]) ->
+ edoc_tags:scan_lines(Ss, L) ++ scan_tags(Es);
+scan_tags([]) ->
+ [].
+
+%% Check the set of found tags (depending on context).
+%% Completely unknown tags are filtered out with a warning.
+
+check_tags(Ts0, Tags, Where) ->
+ Ts = edoc_tags:filter_tags(Ts0, Tags#tags.names, Where),
+ case check_tags_1(Ts, Tags, Where) of
+ false -> Ts;
+ true -> exit(error)
+ end.
+
+check_tags_1(Ts, Tags, {_, module} = Where) ->
+ Allow = Tags#tags.module,
+ Single = Tags#tags.single,
+ edoc_tags:check_tags(Ts, Allow, Single, Where);
+check_tags_1(Ts, Tags, {_, footer} = Where) ->
+ Allow = Tags#tags.footer,
+ Single = Tags#tags.single,
+ edoc_tags:check_tags(Ts, Allow, Single, Where);
+check_tags_1(Ts, Tags, Where) ->
+ Allow = Tags#tags.function,
+ Single = Tags#tags.single,
+ edoc_tags:check_tags(Ts, Allow, Single, Where).
+
+%% Macros for modules
+
+module_macros(Env) ->
+ [{module, atom_to_list(Env#env.module)}]
+ ++ edoc_macros:std_macros(Env).
+
+%% Macros for reading auxiliary edoc-files
+
+file_macros(_Context, Env) ->
+ edoc_macros:std_macros(Env).
diff --git a/lib/edoc/src/edoc_layout.erl b/lib/edoc/src/edoc_layout.erl
new file mode 100644
index 0000000000..900f0b3040
--- /dev/null
+++ b/lib/edoc/src/edoc_layout.erl
@@ -0,0 +1,875 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @author Richard Carlsson <[email protected]>
+%% @copyright 2001-2006 Richard Carlsson
+%% @see edoc
+%% @end
+%% =====================================================================
+
+%% @doc The standard HTML layout module for EDoc. See the {@link edoc}
+%% module for details on usage.
+
+%% Note that this is written so that it is *not* depending on edoc.hrl!
+
+-module(edoc_layout).
+
+-export([module/2, package/2, overview/2, type/1]).
+
+-import(edoc_report, [report/2]).
+
+-include("xmerl.hrl").
+
+-define(HTML_EXPORT, xmerl_html).
+-define(DEFAULT_XML_EXPORT, ?HTML_EXPORT).
+-define(OVERVIEW_SUMMARY, "overview-summary.html").
+-define(STYLESHEET, "stylesheet.css").
+-define(NL, "\n").
+-define(DESCRIPTION_TITLE, "Description").
+-define(DESCRIPTION_LABEL, "description").
+-define(DATA_TYPES_TITLE, "Data Types").
+-define(DATA_TYPES_LABEL, "types").
+-define(FUNCTION_INDEX_TITLE, "Function Index").
+-define(FUNCTION_INDEX_LABEL, "index").
+-define(FUNCTIONS_TITLE, "Function Details").
+-define(FUNCTIONS_LABEL, "functions").
+
+
+%% @doc The layout function.
+%%
+%% Options to the standard layout:
+%% <dl>
+%% <dt>{@type {index_columns, integer()@}}
+%% </dt>
+%% <dd>Specifies the number of column pairs used for the function
+%% index tables. The default value is 1.
+%% </dd>
+%% <dt>{@type {stylesheet, string()@}}
+%% </dt>
+%% <dd>Specifies the URI used for referencing the stylesheet. The
+%% default value is `"stylesheet.css"'. If an empty string is
+%% specified, no stylesheet reference will be generated.
+%% </dd>
+%% <dt>{@type {sort_functions, bool()@}}
+%% </dt>
+%% <dd>If `true', the detailed function descriptions are listed by
+%% name, otherwise they are listed in the order of occurrence in
+%% the source file. The default value is `true'.
+%% </dd>
+%% <dt>{@type {xml_export, Module::atom()@}}
+%% </dt>
+%% <dd>Specifies an {@link //xmerl. `xmerl'} callback module to be
+%% used for exporting the documentation. See {@link
+%% //xmerl/xmerl:export_simple/3} for details.
+%% </dd>
+%% </dl>
+%%
+%% @see edoc:layout/2
+
+%% NEW-OPTIONS: xml_export, index_columns, stylesheet
+
+module(Element, Options) ->
+ XML = layout_module(Element, init_opts(Element, Options)),
+ Export = proplists:get_value(xml_export, Options,
+ ?DEFAULT_XML_EXPORT),
+ xmerl:export_simple(XML, Export, []).
+
+% Put layout options in a data structure for easier access.
+
+%% %Commented out until it can be made private
+%% %@type opts() = #opts{root = string(),
+%% % stylesheet = string(),
+%% % index_columns = integer()}
+
+-record(opts, {root, stylesheet, index_columns, sort_functions}).
+
+init_opts(Element, Options) ->
+ R = #opts{root = get_attrval(root, Element),
+ index_columns = proplists:get_value(index_columns,
+ Options, 1),
+ sort_functions = proplists:get_value(sort_functions,
+ Options, true)
+ },
+ case proplists:get_value(stylesheet, Options) of
+ undefined ->
+ S = edoc_lib:join_uri(R#opts.root, ?STYLESHEET),
+ R#opts{stylesheet = S};
+ "" ->
+ R; % don't use any stylesheet
+ S when is_list(S) ->
+ R#opts{stylesheet = S};
+ _ ->
+ report("bad value for option `stylesheet'.", []),
+ exit(error)
+ end.
+
+
+%% =====================================================================
+%% XML-BASED LAYOUT ENGINE
+%% =====================================================================
+
+%% We assume that we have expanded XML data.
+
+%% <!ELEMENT module (behaviour*, description?, author*, copyright?,
+%% version?, since?, deprecated?, see*, reference*,
+%% todo?, typedecls?, functions)>
+%% <!ATTLIST module
+%% name CDATA #REQUIRED
+%% private NMTOKEN(yes | no) #IMPLIED
+%% root CDATA #IMPLIED>
+%% <!ELEMENT behaviour (#PCDATA)>
+%% <!ATTLIST behaviour
+%% href CDATA #IMPLIED>
+%% <!ELEMENT description (briefDescription, fullDescription?)>
+%% <!ELEMENT briefDescription (#PCDATA)>
+%% <!ELEMENT fullDescription (#PCDATA)>
+%% <!ELEMENT author EMPTY>
+%% <!ATTLIST author
+%% name CDATA #REQUIRED
+%% email CDATA #IMPLIED
+%% website CDATA #IMPLIED>
+%% <!ELEMENT version (#PCDATA)>
+%% <!ELEMENT since (#PCDATA)>
+%% <!ELEMENT copyright (#PCDATA)>
+%% <!ELEMENT deprecated (description)>
+%% <!ELEMENT see (#PCDATA)>
+%% <!ATTLIST see
+%% name CDATA #REQUIRED
+%% href CDATA #IMPLIED>
+%% <!ELEMENT reference (#PCDATA)>
+%% <!ELEMENT todo (#PCDATA)>
+%% <!ELEMENT typedecls (typedecl+)>
+%% <!ELEMENT functions (function+)>
+
+%% TODO: improve layout of parameterized modules
+
+layout_module(#xmlElement{name = module, content = Es}=E, Opts) ->
+ Args = module_params(get_content(args, Es)),
+ Name = get_attrval(name, E),
+ Title = case get_elem(args, Es) of
+ [] -> ["Module ", Name];
+ _ -> ["Abstract module ", Name, " [", {Args}, "]"]
+ end,
+ Desc = get_content(description, Es),
+ ShortDesc = get_content(briefDescription, Desc),
+ FullDesc = get_content(fullDescription, Desc),
+ Functions = [{function_name(E), E} || E <- get_content(functions, Es)],
+ Types = [{type_name(E), E} || E <- get_content(typedecls, Es)],
+ SortedFs = lists:sort(Functions),
+ Body = (navigation("top")
+ ++ [?NL, hr, ?NL, ?NL, {h1, Title}, ?NL]
+ ++ doc_index(FullDesc, Functions, Types)
+ ++ ShortDesc
+ ++ [?NL]
+ ++ copyright(Es)
+ ++ deprecated(Es, "module")
+ ++ [?NL]
+ ++ version(Es)
+ ++ since(Es)
+ ++ behaviours(Es, Name)
+ ++ authors(Es)
+ ++ references(Es)
+ ++ sees(Es)
+ ++ todos(Es)
+ ++ if FullDesc == [] -> [];
+ true -> [?NL,
+ {h2, [{a, [{name, "description"}],
+ ["Description"]}]}
+ | FullDesc]
+ end
+ ++ types(lists:sort(Types))
+ ++ function_index(SortedFs, Opts#opts.index_columns)
+ ++ if Opts#opts.sort_functions -> functions(SortedFs);
+ true -> functions(Functions)
+ end
+ ++ [hr, ?NL]
+ ++ navigation("bottom")
+ ++ timestamp()),
+ xhtml(Title, stylesheet(Opts), Body).
+
+module_params(Es) ->
+ As = [{get_text(argName, Es1),
+ get_content(fullDescription, get_content(description, Es1))}
+ || #xmlElement{content = Es1} <- Es],
+ case As of
+ [] -> [];
+ [First | Rest] ->
+ [element(1, First) | [ {[", ",A]} || {A, _D} <- Rest]]
+ end.
+
+timestamp() ->
+ [?NL, {p, [{i, [io_lib:fwrite("Generated by EDoc, ~s, ~s.",
+ [edoc_lib:datestr(date()),
+ edoc_lib:timestr(time())])
+ ]}]},
+ ?NL].
+
+stylesheet(Opts) ->
+ case Opts#opts.stylesheet of
+ undefined ->
+ [];
+ CSS ->
+ [{link, [{rel, "stylesheet"},
+ {type, "text/css"},
+ {href, CSS},
+ {title, "EDoc"}], []},
+ ?NL]
+ end.
+
+navigation(Where) ->
+ [?NL,
+ {'div', [{class, "navbar"}],
+ [{a, [{name, "#navbar_" ++ Where}], []},
+ {table, [{width, "100%"}, {border,0},
+ {cellspacing, 0}, {cellpadding, 2},
+ {summary, "navigation bar"}],
+ [{tr,
+ [{td, [{a, [{href, ?OVERVIEW_SUMMARY}, {target,"overviewFrame"}],
+ ["Overview"]}]},
+ {td, [{a, [{href, "http://www.erlang.org/"}],
+ [{img, [{src, "erlang.png"}, {align, "right"},
+ {border, 0}, {alt, "erlang logo"}],
+ []}]}
+ ]}
+ ]}
+ ]}
+ ]}
+ ].
+
+doc_index(FullDesc, Functions, Types) ->
+ case doc_index_rows(FullDesc, Functions, Types) of
+ [] -> [];
+ Rs ->
+ [{ul, [{class, "index"}],
+ [{li, [{a, [{href, local_label(R)}], [T]}]}
+ || {T, R} <- Rs]}]
+ end.
+
+doc_index_rows(FullDesc, Functions, Types) ->
+ (if FullDesc == [] -> [];
+ true -> [{?DESCRIPTION_TITLE, ?DESCRIPTION_LABEL}]
+ end
+ ++ if Types == [] -> [];
+ true -> [{?DATA_TYPES_TITLE, ?DATA_TYPES_LABEL}]
+ end
+ ++ if Functions == [] -> [];
+ true -> [{?FUNCTION_INDEX_TITLE, ?FUNCTION_INDEX_LABEL},
+ {?FUNCTIONS_TITLE, ?FUNCTIONS_LABEL}]
+ end).
+
+function_index(Fs, Cols) ->
+ case function_index_rows(Fs, Cols, []) of
+ [] -> [];
+ Rows ->
+ [?NL,
+ {h2, [{a, [{name, ?FUNCTION_INDEX_LABEL}],
+ [?FUNCTION_INDEX_TITLE]}]},
+ ?NL,
+ {table, [{width, "100%"}, {border, 1},
+ {cellspacing,0}, {cellpadding,2},
+ {summary, "function index"}],
+ Rows},
+ ?NL]
+ end.
+
+function_index_rows(Fs, Cols, Title) ->
+ Rows = (length(Fs) + (Cols - 1)) div Cols,
+ (if Title == [] -> [];
+ true -> [{tr, [{th, [{colspan, Cols * 2}, {align, left}],
+ [Title]}]},
+ ?NL]
+ end
+ ++ lists:flatmap(fun index_row/1,
+ edoc_lib:transpose(edoc_lib:segment(Fs, Rows)))).
+
+index_row(Fs) ->
+ [{tr, lists:flatmap(fun index_col/1, Fs)}, ?NL].
+
+index_col({Name, F=#xmlElement{content = Es}}) ->
+ [{td, [{valign, "top"}],
+ label_href(function_header(Name, F, "*"), F)},
+ {td, index_desc(Es)}].
+
+index_desc(Es) ->
+ Desc = get_content(description, Es),
+ (case get_content(deprecated, Es) of
+ [] -> [];
+ _ -> ["(", {em, ["Deprecated"]}, ".) "]
+ end
+ ++ case get_content(briefDescription, Desc) of
+ [] ->
+ equiv(Es); % no description at all if no equiv
+ ShortDesc ->
+ ShortDesc
+ end).
+
+label_href(Content, F) ->
+ case get_attrval(label, F) of
+ "" -> Content;
+ Ref -> [{a, [{href, local_label(Ref)}], Content}]
+ end.
+
+%% <!ELEMENT function (args, typespec?, returns?, throws?, equiv?,
+%% description?, since?, deprecated?, see*, todo?)>
+%% <!ATTLIST function
+%% name CDATA #REQUIRED
+%% arity CDATA #REQUIRED
+%% exported NMTOKEN(yes | no) #REQUIRED
+%% label CDATA #IMPLIED>
+%% <!ELEMENT args (arg*)>
+%% <!ELEMENT equiv (expr, see?)>
+%% <!ELEMENT expr (#PCDATA)>
+
+functions(Fs) ->
+ Es = lists:flatmap(fun ({Name, E}) -> function(Name, E) end, Fs),
+ if Es == [] -> [];
+ true ->
+ [?NL,
+ {h2, [{a, [{name, ?FUNCTIONS_LABEL}], [?FUNCTIONS_TITLE]}]},
+ ?NL | Es]
+ end.
+
+function(Name, E=#xmlElement{content = Es}) ->
+ ([?NL,
+ {h3, [{class, "function"}],
+ label_anchor(function_header(Name, E, " *"), E)},
+ ?NL]
+ ++ [{'div', [{class, "spec"}],
+ [?NL,
+ {p,
+ case typespec(get_content(typespec, Es)) of
+ [] ->
+ signature(get_content(args, Es),
+ get_attrval(name, E));
+ Spec -> Spec
+ end},
+ ?NL]
+ ++ case params(get_content(args, Es)) of
+ [] -> [];
+ Ps -> [{p, Ps}, ?NL]
+ end
+ ++ case returns(get_content(returns, Es)) of
+ [] -> [];
+ Rs -> [{p, Rs}, ?NL]
+ end}]
+ ++ throws(Es)
+ ++ equiv_p(Es)
+ ++ deprecated(Es, "function")
+ ++ fulldesc(Es)
+ ++ since(Es)
+ ++ sees(Es)
+ ++ todos(Es)).
+
+function_name(E) ->
+ atom(get_attrval(name, E)) ++ "/" ++ get_attrval(arity, E).
+
+function_header(Name, E, Private) ->
+ case is_exported(E) of
+ true -> [Name];
+ false -> [Name, Private]
+ end.
+
+is_exported(E) ->
+ case get_attrval(exported, E) of
+ "yes" -> true;
+ _ -> false
+ end.
+
+label_anchor(Content, E) ->
+ case get_attrval(label, E) of
+ "" -> Content;
+ Ref -> [{a, [{name, Ref}], Content}]
+ end.
+
+%% <!ELEMENT args (arg*)>
+%% <!ELEMENT arg (argName, description?)>
+%% <!ELEMENT argName (#PCDATA)>
+
+%% This is currently only done for functions without type spec.
+
+signature(Es, Name) ->
+ [{tt, [Name, "("] ++ seq(fun arg/1, Es) ++ [") -> any()"]}].
+
+arg(#xmlElement{content = Es}) ->
+ [get_text(argName, Es)].
+
+%% parameter and return value descriptions (if any)
+
+params(Es) ->
+ As = [{get_text(argName, Es1),
+ get_content(fullDescription, get_content(description, Es1))}
+ || #xmlElement{content = Es1} <- Es],
+ As1 = [A || A <- As, element(2, A) /= []],
+ if As1 == [] ->
+ [];
+ true ->
+ [ { [{tt, [A]}, ": "] ++ D ++ [br, ?NL] }
+ || {A, D} <- As1]
+ end.
+
+returns(Es) ->
+ case get_content(fullDescription, get_content(description, Es)) of
+ [] ->
+ [];
+ D ->
+ ["returns: "] ++ D
+ end.
+
+%% <!ELEMENT throws (type, localdef*)>
+
+throws(Es) ->
+ case get_content(throws, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, (["throws ", {tt, t_utype(get_elem(type, Es1))}]
+ ++ local_defs(get_elem(localdef, Es1)))},
+ ?NL]
+ end.
+
+%% <!ELEMENT typespec (erlangName, type, localdef*)>
+
+typespec([]) -> [];
+typespec(Es) ->
+ [{tt, ([t_name(get_elem(erlangName, Es))]
+ ++ t_utype(get_elem(type, Es)))}]
+ ++ local_defs(get_elem(localdef, Es)).
+
+%% <!ELEMENT typedecl (typedef, description?)>
+%% <!ELEMENT typedef (erlangName, argtypes, type?, localdef*)>
+
+types([]) -> [];
+types(Ts) ->
+ Es = lists:flatmap(fun ({Name, E}) -> typedecl(Name, E) end, Ts),
+ [?NL,
+ {h2, [{a, [{name, ?DATA_TYPES_LABEL}],
+ [?DATA_TYPES_TITLE]}]},
+ ?NL | Es].
+
+typedecl(Name, E=#xmlElement{content = Es}) ->
+ ([?NL, {h3, [{class, "typedecl"}], label_anchor([Name, "()"], E)}, ?NL]
+ ++ [{p, typedef(get_content(typedef, Es))}, ?NL]
+ ++ fulldesc(Es)).
+
+type_name(#xmlElement{content = Es}) ->
+ t_name(get_elem(erlangName, get_content(typedef, Es))).
+
+typedef(Es) ->
+ Name = ([t_name(get_elem(erlangName, Es)), "("]
+ ++ seq(fun t_utype_elem/1, get_content(argtypes, Es), [")"])),
+ (case get_elem(type, Es) of
+ [] -> [{b, ["abstract datatype"]}, ": ", {tt, Name}];
+ Type ->
+ [{tt, Name ++ [" = "] ++ t_utype(Type)}]
+ end
+ ++ local_defs(get_elem(localdef, Es))).
+
+local_defs([]) -> [];
+local_defs(Es) ->
+ [?NL,
+ {ul, [{class, "definitions"}],
+ lists:concat([[{li, [{tt, localdef(E)}]}, ?NL] || E <- Es])}].
+
+localdef(E = #xmlElement{content = Es}) ->
+ (case get_elem(typevar, Es) of
+ [] ->
+ label_anchor(t_abstype(get_content(abstype, Es)), E);
+ [V] ->
+ t_var(V)
+ end
+ ++ [" = "] ++ t_utype(get_elem(type, Es))).
+
+fulldesc(Es) ->
+ case get_content(fullDescription, get_content(description, Es)) of
+ [] -> [?NL];
+ Desc -> [{p, Desc}, ?NL]
+ end.
+
+sees(Es) ->
+ case get_elem(see, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{b, ["See also:"]}, " "] ++ seq(fun see/1, Es1, ["."])},
+ ?NL]
+ end.
+
+see(E=#xmlElement{content = Es}) ->
+ see(E, Es).
+
+see(E, Es) ->
+ case href(E) of
+ [] -> Es;
+ Ref ->
+ [{a, Ref, Es}]
+ end.
+
+href(E) ->
+ case get_attrval(href, E) of
+ "" -> [];
+ URI ->
+ T = case get_attrval(target, E) of
+ "" -> [];
+ S -> [{target, S}]
+ end,
+ [{href, URI} | T]
+ end.
+
+equiv_p(Es) ->
+ equiv(Es, true).
+
+equiv(Es) ->
+ equiv(Es, false).
+
+equiv(Es, P) ->
+ case get_content(equiv, Es) of
+ [] -> [];
+ Es1 ->
+ case get_content(expr, Es1) of
+ [] -> [];
+ [Expr] ->
+ Expr1 = [{tt, [Expr]}],
+ Expr2 = case get_elem(see, Es1) of
+ [] ->
+ Expr1;
+ [E=#xmlElement{}] ->
+ see(E, Expr1)
+ end,
+ Txt = ["Equivalent to "] ++ Expr2 ++ ["."],
+ (case P of
+ true -> [{p, Txt}];
+ false -> Txt
+ end
+ ++ [?NL])
+ end
+ end.
+
+copyright(Es) ->
+ case get_content(copyright, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, ["Copyright \251 " | Es1]}, ?NL]
+ end.
+
+version(Es) ->
+ case get_content(version, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{b, ["Version:"]}, " " | Es1]}, ?NL]
+ end.
+
+since(Es) ->
+ case get_content(since, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{b, ["Introduced in:"]}, " " | Es1]}, ?NL]
+ end.
+
+deprecated(Es, S) ->
+ Es1 = get_content(description, get_content(deprecated, Es)),
+ case get_content(fullDescription, Es1) of
+ [] -> [];
+ Es2 ->
+ [{p, [{b, ["This " ++ S ++ " is deprecated:"]}, " " | Es2]},
+ ?NL]
+ end.
+
+behaviours(Es, Name) ->
+ (case get_elem(behaviour, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, ([{b, ["Behaviours:"]}, " "]
+ ++ seq(fun behaviour/1, Es1, ["."]))},
+ ?NL]
+ end
+ ++
+ case get_content(callbacks, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, ([{b, ["This module defines the ", {tt, [Name]},
+ " behaviour."]},
+ br, " Required callback functions: "]
+ ++ seq(fun callback/1, Es1, ["."]))},
+ ?NL]
+ end).
+
+behaviour(E=#xmlElement{content = Es}) ->
+ see(E, [{tt, Es}]).
+
+callback(E=#xmlElement{}) ->
+ Name = get_attrval(name, E),
+ Arity = get_attrval(arity, E),
+ [{tt, [Name, "/", Arity]}].
+
+authors(Es) ->
+ case get_elem(author, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{b, ["Authors:"]}, " "] ++ seq(fun author/1, Es1, ["."])},
+ ?NL]
+ end.
+
+atom(String) ->
+ io_lib:write_atom(list_to_atom(String)).
+
+%% <!ATTLIST author
+%% name CDATA #REQUIRED
+%% email CDATA #IMPLIED
+%% website CDATA #IMPLIED>
+
+author(E=#xmlElement{}) ->
+ Name = get_attrval(name, E),
+ Mail = get_attrval(email, E),
+ URI = get_attrval(website, E),
+ (if Name == Mail ->
+ [{a, [{href, "mailto:" ++ Mail}],[{tt, [Mail]}]}];
+ true ->
+ if Mail == "" -> [Name];
+ true -> [Name, " (", {a, [{href, "mailto:" ++ Mail}],
+ [{tt, [Mail]}]}, ")"]
+ end
+ end
+ ++ if URI == "" ->
+ [];
+ true ->
+ [" [", {em, ["web site:"]}, " ",
+ {tt, [{a, [{href, URI}, {target, "_top"}], [URI]}]},
+ "]"]
+ end).
+
+references(Es) ->
+ case get_elem(reference, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{b, ["References"]},
+ {ul, [{li, C} || #xmlElement{content = C} <- Es1]}]},
+ ?NL]
+ end.
+
+todos(Es) ->
+ case get_elem(todo, Es) of
+ [] -> [];
+ Es1 ->
+ Todos = [{li, [{font, [{color,red}], C}]}
+ || #xmlElement{content = C} <- Es1],
+ [{p, [{b, [{font, [{color,red}], ["To do"]}]},
+ {ul, Todos}]},
+ ?NL]
+ end.
+
+t_name([E]) ->
+ N = get_attrval(name, E),
+ case get_attrval(module, E) of
+ "" -> atom(N);
+ M ->
+ S = atom(M) ++ ":" ++ atom(N),
+ case get_attrval(app, E) of
+ "" -> S;
+ A -> "//" ++ atom(A) ++ "/" ++ S
+ end
+ end.
+
+t_utype([E]) ->
+ t_utype_elem(E).
+
+t_utype_elem(E=#xmlElement{content = Es}) ->
+ case get_attrval(name, E) of
+ "" -> t_type(Es);
+ Name ->
+ T = t_type(Es),
+ case T of
+ [Name] -> T; % avoid generating "Foo::Foo"
+ T -> [Name] ++ ["::"] ++ T
+ end
+ end.
+
+t_type([E=#xmlElement{name = typevar}]) ->
+ t_var(E);
+t_type([E=#xmlElement{name = atom}]) ->
+ t_atom(E);
+t_type([E=#xmlElement{name = integer}]) ->
+ t_integer(E);
+t_type([E=#xmlElement{name = float}]) ->
+ t_float(E);
+t_type([#xmlElement{name = nil}]) ->
+ t_nil();
+t_type([#xmlElement{name = list, content = Es}]) ->
+ t_list(Es);
+t_type([#xmlElement{name = tuple, content = Es}]) ->
+ t_tuple(Es);
+t_type([#xmlElement{name = 'fun', content = Es}]) ->
+ t_fun(Es);
+t_type([#xmlElement{name = record, content = Es}]) ->
+ t_record(Es);
+t_type([E = #xmlElement{name = abstype, content = Es}]) ->
+ T = t_abstype(Es),
+ see(E, T);
+t_type([#xmlElement{name = union, content = Es}]) ->
+ t_union(Es).
+
+t_var(E) ->
+ [get_attrval(name, E)].
+
+t_atom(E) ->
+ [get_attrval(value, E)].
+
+t_integer(E) ->
+ [get_attrval(value, E)].
+
+t_float(E) ->
+ [get_attrval(value, E)].
+
+t_nil() ->
+ ["[]"].
+
+t_list(Es) ->
+ ["["] ++ t_utype(get_elem(type, Es)) ++ ["]"].
+
+t_tuple(Es) ->
+ ["{"] ++ seq(fun t_utype_elem/1, Es, ["}"]).
+
+t_fun(Es) ->
+ ["("] ++ seq(fun t_utype_elem/1, get_content(argtypes, Es),
+ [") -> "] ++ t_utype(get_elem(type, Es))).
+
+t_record(Es) ->
+ ["#"] ++ t_type(get_elem(atom, Es)) ++ ["{"]
+ ++ seq(fun t_field/1, get_elem(field, Es), ["}"]).
+
+t_field(#xmlElement{content = Es}) ->
+ t_type(get_elem(atom, Es)) ++ [" = "] ++ t_utype(get_elem(type, Es)).
+
+t_abstype(Es) ->
+ ([t_name(get_elem(erlangName, Es)), "("]
+ ++ seq(fun t_utype_elem/1, get_elem(type, Es), [")"])).
+
+t_union(Es) ->
+ seq(fun t_utype_elem/1, Es, " | ", []).
+
+seq(F, Es) ->
+ seq(F, Es, []).
+
+seq(F, Es, Tail) ->
+ seq(F, Es, ", ", Tail).
+
+seq(F, [E], _Sep, Tail) ->
+ F(E) ++ Tail;
+seq(F, [E | Es], Sep, Tail) ->
+ F(E) ++ [Sep] ++ seq(F, Es, Sep, Tail);
+seq(_F, [], _Sep, Tail) ->
+ Tail.
+
+get_elem(Name, [#xmlElement{name = Name} = E | Es]) ->
+ [E | get_elem(Name, Es)];
+get_elem(Name, [_ | Es]) ->
+ get_elem(Name, Es);
+get_elem(_, []) ->
+ [].
+
+get_attr(Name, [#xmlAttribute{name = Name} = A | As]) ->
+ [A | get_attr(Name, As)];
+get_attr(Name, [_ | As]) ->
+ get_attr(Name, As);
+get_attr(_, []) ->
+ [].
+
+get_attrval(Name, #xmlElement{attributes = As}) ->
+ case get_attr(Name, As) of
+ [#xmlAttribute{value = V}] ->
+ V;
+ [] -> ""
+ end.
+
+get_content(Name, Es) ->
+ case get_elem(Name, Es) of
+ [#xmlElement{content = Es1}] ->
+ Es1;
+ [] -> []
+ end.
+
+get_text(Name, Es) ->
+ case get_content(Name, Es) of
+ [#xmlText{value = Text}] ->
+ Text;
+ [] -> ""
+ end.
+
+local_label(R) ->
+ "#" ++ R.
+
+xhtml(Title, CSS, Body) ->
+ [{html, [?NL,
+ {head, [?NL,
+ {title, Title},
+ ?NL] ++ CSS},
+ ?NL,
+ {body, [{bgcolor, "white"}], Body},
+ ?NL]
+ },
+ ?NL].
+
+%% ---------------------------------------------------------------------
+
+type(E) ->
+ type(E, []).
+
+type(E, Ds) ->
+ xmerl:export_simple_content(t_utype_elem(E) ++ local_defs(Ds),
+ ?HTML_EXPORT).
+
+package(E=#xmlElement{name = package, content = Es}, Options) ->
+ Opts = init_opts(E, Options),
+ Name = get_text(packageName, Es),
+ Title = ["Package ", Name],
+ Desc = get_content(description, Es),
+% ShortDesc = get_content(briefDescription, Desc),
+ FullDesc = get_content(fullDescription, Desc),
+ Body = ([?NL, {h1, [Title]}, ?NL]
+% ++ ShortDesc
+ ++ copyright(Es)
+ ++ deprecated(Es, "package")
+ ++ version(Es)
+ ++ since(Es)
+ ++ authors(Es)
+ ++ references(Es)
+ ++ sees(Es)
+ ++ todos(Es)
+ ++ FullDesc),
+ XML = xhtml(Title, stylesheet(Opts), Body),
+ xmerl:export_simple(XML, ?HTML_EXPORT, []).
+
+overview(E=#xmlElement{name = overview, content = Es}, Options) ->
+ Opts = init_opts(E, Options),
+ Title = [get_text(title, Es)],
+ Desc = get_content(description, Es),
+% ShortDesc = get_content(briefDescription, Desc),
+ FullDesc = get_content(fullDescription, Desc),
+ Body = (navigation("top")
+ ++ [?NL, {h1, [Title]}, ?NL]
+% ++ ShortDesc
+ ++ copyright(Es)
+ ++ version(Es)
+ ++ since(Es)
+ ++ authors(Es)
+ ++ references(Es)
+ ++ sees(Es)
+ ++ todos(Es)
+ ++ FullDesc
+ ++ [?NL, hr]
+ ++ navigation("bottom")
+ ++ timestamp()),
+ XML = xhtml(Title, stylesheet(Opts), Body),
+ xmerl:export_simple(XML, ?HTML_EXPORT, []).
diff --git a/lib/edoc/src/edoc_lib.erl b/lib/edoc/src/edoc_lib.erl
new file mode 100644
index 0000000000..47e61f7932
--- /dev/null
+++ b/lib/edoc/src/edoc_lib.erl
@@ -0,0 +1,998 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @private
+%% @copyright 2001-2003 Richard Carlsson
+%% @author Richard Carlsson <[email protected]>
+%% @see edoc
+%% @end
+%% =====================================================================
+
+%% @doc Utility functions for EDoc.
+
+-module(edoc_lib).
+
+-export([count/2, lines/1, split_at/2, split_at_stop/1,
+ split_at_space/1, filename/1, transpose/1, segment/2,
+ get_first_sentence/1, is_space/1, strip_space/1, parse_expr/2,
+ parse_contact/2, escape_uri/1, join_uri/2, is_relative_uri/1,
+ is_name/1, to_label/1, find_doc_dirs/0, find_sources/2,
+ find_sources/3, find_file/3, try_subdir/2, unique/1,
+ write_file/3, write_file/4, write_info_file/4,
+ read_info_file/1, get_doc_env/1, get_doc_env/4, copy_file/2,
+ uri_get/1, run_doclet/2, run_layout/2,
+ simplify_path/1, timestr/1, datestr/1]).
+
+-import(edoc_report, [report/2, warning/2]).
+
+-include("edoc.hrl").
+-include("xmerl.hrl").
+
+-define(FILE_BASE, "/").
+
+
+%% ---------------------------------------------------------------------
+%% List and string utilities
+
+timestr({H,M,Sec}) ->
+ lists:flatten(io_lib:fwrite("~2.2.0w:~2.2.0w:~2.2.0w",[H,M,Sec])).
+
+datestr({Y,M,D}) ->
+ Ms = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
+ "Oct", "Nov", "Dec"],
+ lists:flatten(io_lib:fwrite("~s ~w ~w",[lists:nth(M, Ms),D,Y])).
+
+count(X, Xs) ->
+ count(X, Xs, 0).
+
+count(X, [X | Xs], N) ->
+ count(X, Xs, N + 1);
+count(X, [_ | Xs], N) ->
+ count(X, Xs, N);
+count(_X, [], N) ->
+ N.
+
+lines(Cs) ->
+ lines(Cs, [], []).
+
+lines([$\n | Cs], As, Ls) ->
+ lines(Cs, [], [lists:reverse(As) | Ls]);
+lines([C | Cs], As, Ls) ->
+ lines(Cs, [C | As], Ls);
+lines([], As, Ls) ->
+ lists:reverse([lists:reverse(As) | Ls]).
+
+split_at(Cs, K) ->
+ split_at(Cs, K, []).
+
+split_at([K | Cs], K, As) ->
+ {lists:reverse(As), Cs};
+split_at([C | Cs], K, As) ->
+ split_at(Cs, K, [C | As]);
+split_at([], _K, As) ->
+ {lists:reverse(As), []}.
+
+split_at_stop(Cs) ->
+ split_at_stop(Cs, []).
+
+split_at_stop([$., $\s | Cs], As) ->
+ {lists:reverse(As), Cs};
+split_at_stop([$., $\t | Cs], As) ->
+ {lists:reverse(As), Cs};
+split_at_stop([$., $\n | Cs], As) ->
+ {lists:reverse(As), Cs};
+split_at_stop([$.], As) ->
+ {lists:reverse(As), []};
+split_at_stop([C | Cs], As) ->
+ split_at_stop(Cs, [C | As]);
+split_at_stop([], As) ->
+ {lists:reverse(As), []}.
+
+split_at_space(Cs) ->
+ split_at_space(Cs, []).
+
+split_at_space([$\s | Cs], As) ->
+ {lists:reverse(As), Cs};
+split_at_space([$\t | Cs], As) ->
+ {lists:reverse(As), Cs};
+split_at_space([$\n | Cs], As) ->
+ {lists:reverse(As), Cs};
+split_at_space([C | Cs], As) ->
+ split_at_space(Cs, [C | As]);
+split_at_space([], As) ->
+ {lists:reverse(As), []}.
+
+is_space([$\s | Cs]) -> is_space(Cs);
+is_space([$\t | Cs]) -> is_space(Cs);
+is_space([$\n | Cs]) -> is_space(Cs);
+is_space([_C | _Cs]) -> false;
+is_space([]) -> true.
+
+strip_space([$\s | Cs]) -> strip_space(Cs);
+strip_space([$\t | Cs]) -> strip_space(Cs);
+strip_space([$\n | Cs]) -> strip_space(Cs);
+strip_space(Cs) -> Cs.
+
+segment(Es, N) ->
+ segment(Es, [], [], 0, N).
+
+segment([E | Es], As, Cs, N, M) when N < M ->
+ segment(Es, [E | As], Cs, N + 1, M);
+segment([_ | _] = Es, As, Cs, _N, M) ->
+ segment(Es, [], [lists:reverse(As) | Cs], 0, M);
+segment([], [], Cs, _N, _M) ->
+ lists:reverse(Cs);
+segment([], As, Cs, _N, _M) ->
+ lists:reverse([lists:reverse(As) | Cs]).
+
+transpose([]) -> [];
+transpose([[] | Xss]) -> transpose(Xss);
+transpose([[X | Xs] | Xss]) ->
+ [[X | [H || [H | _T] <- Xss]]
+ | transpose([Xs | [T || [_H | T] <- Xss]])].
+
+%% Note that the parser will not produce two adjacent text segments;
+%% thus, if a text segment ends with a period character, it marks the
+%% end of the summary sentence only if it is also the last segment in
+%% the list, or is followed by a 'p' or 'br' ("whitespace") element.
+
+get_first_sentence([#xmlElement{name = p, content = Es} | _]) ->
+ %% Descend into initial paragraph.
+ get_first_sentence_1(Es);
+get_first_sentence(Es) ->
+ get_first_sentence_1(Es).
+
+get_first_sentence_1([E = #xmlText{value = Txt} | Es]) ->
+ Last = case Es of
+ [#xmlElement{name = p} | _] -> true;
+ [#xmlElement{name = br} | _] -> true;
+ [] -> true;
+ _ -> false
+ end,
+ case end_of_sentence(Txt, Last) of
+ {value, Txt1} ->
+ [E#xmlText{value = Txt1}];
+ none ->
+ [E | get_first_sentence_1(Es)]
+ end;
+get_first_sentence_1([E | Es]) ->
+ % Skip non-text segments - don't descend further
+ [E | get_first_sentence_1(Es)];
+get_first_sentence_1([]) ->
+ [].
+
+end_of_sentence(Cs, Last) ->
+ end_of_sentence(Cs, Last, []).
+
+%% We detect '.' and '!' as end-of-sentence markers.
+
+end_of_sentence([C=$., $\s | _], _, As) ->
+ end_of_sentence_1(C, true, As);
+end_of_sentence([C=$., $\t | _], _, As) ->
+ end_of_sentence_1(C, true, As);
+end_of_sentence([C=$., $\n | _], _, As) ->
+ end_of_sentence_1(C, true, As);
+end_of_sentence([C=$.], Last, As) ->
+ end_of_sentence_1(C, Last, As);
+end_of_sentence([C=$!, $\s | _], _, As) ->
+ end_of_sentence_1(C, true, As);
+end_of_sentence([C=$!, $\t | _], _, As) ->
+ end_of_sentence_1(C, true, As);
+end_of_sentence([C=$!, $\n | _], _, As) ->
+ end_of_sentence_1(C, true, As);
+end_of_sentence([C=$!], Last, As) ->
+ end_of_sentence_1(C, Last, As);
+end_of_sentence([C | Cs], Last, As) ->
+ end_of_sentence(Cs, Last, [C | As]);
+end_of_sentence([], Last, As) ->
+ end_of_sentence_1($., Last, strip_space(As)). % add a '.'
+
+end_of_sentence_1(C, true, As) ->
+ {value, lists:reverse([C | As])};
+end_of_sentence_1(_, false, _) ->
+ none.
+
+%% For handling ISO 8859-1 (Latin-1) we use the following information:
+%%
+%% 000 - 037 NUL - US control
+%% 040 - 057 SPC - / punctuation
+%% 060 - 071 0 - 9 digit
+%% 072 - 100 : - @ punctuation
+%% 101 - 132 A - Z uppercase
+%% 133 - 140 [ - ` punctuation
+%% 141 - 172 a - z lowercase
+%% 173 - 176 { - ~ punctuation
+%% 177 DEL control
+%% 200 - 237 control
+%% 240 - 277 NBSP - � punctuation
+%% 300 - 326 � - � uppercase
+%% 327 � punctuation
+%% 330 - 336 � - � uppercase
+%% 337 - 366 � - � lowercase
+%% 367 � punctuation
+%% 370 - 377 � - � lowercase
+
+%% Names must begin with a lowercase letter and contain only
+%% alphanumerics and underscores.
+
+is_name([C | Cs]) when C >= $a, C =< $z ->
+ is_name_1(Cs);
+is_name([C | Cs]) when C >= $\337, C =< $\377, C =/= $\367 ->
+ is_name_1(Cs);
+is_name(_) -> false.
+
+is_name_1([C | Cs]) when C >= $a, C =< $z ->
+ is_name_1(Cs);
+is_name_1([C | Cs]) when C >= $A, C =< $Z ->
+ is_name_1(Cs);
+is_name_1([C | Cs]) when C >= $0, C =< $9 ->
+ is_name_1(Cs);
+is_name_1([C | Cs]) when C >= $\300, C =< $\377, C =/= $\327, C =/= $\367 ->
+ is_name_1(Cs);
+is_name_1([$_ | Cs]) ->
+ is_name_1(Cs);
+is_name_1([]) -> true;
+is_name_1(_) -> false.
+
+to_atom(A) when is_atom(A) -> A;
+to_atom(S) when is_list(S) -> list_to_atom(S).
+
+unique([X | Xs]) -> [X | unique(Xs, X)];
+unique([]) -> [].
+
+unique([X | Xs], X) -> unique(Xs, X);
+unique([X | Xs], _) -> [X | unique(Xs, X)];
+unique([], _) -> [].
+
+
+%% ---------------------------------------------------------------------
+%% Parsing utilities
+
+%% @doc EDoc Erlang expression parsing. For parsing things like the
+%% content of <a href="overview-summary.html#ftag-equiv">`@equiv'</a>
+%% tags, and strings denoting file names, e.g. in @headerfile. Also used
+%% by {@link edoc_run}.
+
+parse_expr(S, L) ->
+ case erl_scan:string(S ++ ".", L) of
+ {ok, Ts, _} ->
+ case erl_parse:parse_exprs(Ts) of
+ {ok, [Expr]} ->
+ Expr;
+ {error, {999999, erl_parse, _}} ->
+ throw_error(eof, L);
+ {error, E} ->
+ throw_error(E, L)
+ end;
+ {error, E, _} ->
+ throw_error(E, L)
+ end.
+
+
+%% @doc EDoc "contact information" parsing. This is the type of the
+%% content in e.g.
+%% <a href="overview-summary.html#mtag-author">`@author'</a> tags.
+
+%% @type info() = #info{name = string(),
+%% mail = string(),
+%% uri = string()}
+
+-record(info, {name = "", email = "", uri = ""}).
+
+parse_contact(S, L) ->
+ I = scan_name(S, L, #info{}, []),
+ {I#info.name, I#info.email, I#info.uri}.
+
+%% The name is taken as the first non-whitespace-only string before,
+%% between, or following the e-mail/URI sections. Subsequent text that
+%% is not e/mail or URI is ignored.
+
+scan_name([$< | Cs], L, I, As) ->
+ case I#info.email of
+ "" ->
+ {Cs1, I1} = scan_email(Cs, L, set_name(I, As), []),
+ scan_name(Cs1, L, I1, []);
+ _ ->
+ throw_error("multiple '<...>' sections.", L)
+ end;
+scan_name([$[ | Cs], L, I, As) ->
+ case I#info.uri of
+ "" ->
+ {Cs1, I1} = scan_uri(Cs, L, set_name(I, As), []),
+ scan_name(Cs1, L, I1, []);
+ _ ->
+ throw_error("multiple '[...]' sections.", L)
+ end;
+scan_name([$\n | Cs], L, I, As) ->
+ scan_name(Cs, L + 1, I, [$\n | As]);
+scan_name([C | Cs], L, I, As) ->
+ scan_name(Cs, L, I, [C | As]);
+scan_name([], _L, I, As) ->
+ set_name(I, As).
+
+scan_uri([$] | Cs], _L, I, As) ->
+ {Cs, I#info{uri = strip_and_reverse(As)}};
+scan_uri([$\n | Cs], L, I, As) ->
+ scan_uri(Cs, L + 1, I, [$\n | As]);
+scan_uri([C | Cs], L, I, As) ->
+ scan_uri(Cs, L, I, [C | As]);
+scan_uri([], L, _I, _As) ->
+ throw_error({missing, $]}, L).
+
+scan_email([$> | Cs], _L, I, As) ->
+ {Cs, I#info{email = strip_and_reverse(As)}};
+scan_email([$\n | Cs], L, I, As) ->
+ scan_email(Cs, L + 1, I, [$\n | As]);
+scan_email([C | Cs], L, I, As) ->
+ scan_email(Cs, L, I, [C | As]);
+scan_email([], L, _I, _As) ->
+ throw_error({missing, $>}, L).
+
+set_name(I, As) ->
+ case I#info.name of
+ "" -> I#info{name = strip_and_reverse(As)};
+ _ -> I
+ end.
+
+strip_and_reverse(As) ->
+ edoc_lib:strip_space(lists:reverse(edoc_lib:strip_space(As))).
+
+
+%% ---------------------------------------------------------------------
+%% URI and Internet
+
+%% This is a conservative URI escaping, which escapes anything that may
+%% not appear in an NMTOKEN ([a-zA-Z0-9]|'.'|'-'|'_'), including ':'.
+%% Characters are first encoded in UTF-8.
+%%
+%% Note that this should *not* be applied to complete URI, but only to
+%% segments that may need escaping, when forming a complete URI.
+%%
+%% TODO: general utf-8 encoding for all of Unicode (0-16#10ffff)
+
+escape_uri([C | Cs]) when C >= $a, C =< $z ->
+ [C | escape_uri(Cs)];
+escape_uri([C | Cs]) when C >= $A, C =< $Z ->
+ [C | escape_uri(Cs)];
+escape_uri([C | Cs]) when C >= $0, C =< $9 ->
+ [C | escape_uri(Cs)];
+escape_uri([C = $. | Cs]) ->
+ [C | escape_uri(Cs)];
+escape_uri([C = $- | Cs]) ->
+ [C | escape_uri(Cs)];
+escape_uri([C = $_ | Cs]) ->
+ [C | escape_uri(Cs)];
+escape_uri([C | Cs]) when C > 16#7f ->
+ %% This assumes that characters are at most 16 bits wide.
+ escape_byte(((C band 16#c0) bsr 6) + 16#c0)
+ ++ escape_byte(C band 16#3f + 16#80)
+ ++ escape_uri(Cs);
+escape_uri([C | Cs]) ->
+ escape_byte(C) ++ escape_uri(Cs);
+escape_uri([]) ->
+ [].
+
+escape_byte(C) ->
+ "%" ++ hex_octet(C).
+
+% utf8([C | Cs]) when C > 16#7f ->
+% [((C band 16#c0) bsr 6) + 16#c0, C band 16#3f ++ 16#80 | utf8(Cs)];
+% utf8([C | Cs]) ->
+% [C | utf8(Cs)];
+% utf8([]) ->
+% [].
+
+hex_octet(N) when N =< 9 ->
+ [$0 + N];
+hex_octet(N) when N > 15 ->
+ hex_octet(N bsr 4) ++ hex_octet(N band 15);
+hex_octet(N) ->
+ [N - 10 + $a].
+
+%% Please note that URI are *not* file names. Don't use the stdlib
+%% 'filename' module for operations on (any parts of) URI.
+
+join_uri(Base, "") ->
+ Base;
+join_uri("", Path) ->
+ Path;
+join_uri(Base, Path) ->
+ Base ++ "/" ++ Path.
+
+%% Check for relative URI; "network paths" ("//...") not included!
+
+is_relative_uri([$: | _]) ->
+ false;
+is_relative_uri([$/, $/ | _]) ->
+ false;
+is_relative_uri([$/ | _]) ->
+ true;
+is_relative_uri([$? | _]) ->
+ true;
+is_relative_uri([$# | _]) ->
+ true;
+is_relative_uri([_ | Cs]) ->
+ is_relative_uri(Cs);
+is_relative_uri([]) ->
+ true.
+
+uri_get("file:///" ++ Path) ->
+ uri_get_file(Path);
+uri_get("file://localhost/" ++ Path) ->
+ uri_get_file(Path);
+uri_get("file://" ++ Path) ->
+ Msg = io_lib:format("cannot handle 'file:' scheme with "
+ "nonlocal network-path: 'file://~s'.",
+ [Path]),
+ {error, Msg};
+uri_get("file:/" ++ Path) ->
+ uri_get_file(Path);
+uri_get("file:" ++ Path) ->
+ Msg = io_lib:format("ignoring malformed URI: 'file:~s'.", [Path]),
+ {error, Msg};
+uri_get("http:" ++ Path) ->
+ uri_get_http("http:" ++ Path);
+uri_get("ftp:" ++ Path) ->
+ uri_get_ftp("ftp:" ++ Path);
+uri_get("//" ++ Path) ->
+ Msg = io_lib:format("cannot access network-path: '//~s'.", [Path]),
+ {error, Msg};
+uri_get(URI) ->
+ case is_relative_uri(URI) of
+ true ->
+ uri_get_file(URI);
+ false ->
+ Msg = io_lib:format("cannot handle URI: '~s'.", [URI]),
+ {error, Msg}
+ end.
+
+uri_get_file(File0) ->
+ File = filename:join(?FILE_BASE, File0),
+ case read_file(File) of
+ {ok, Text} ->
+ {ok, Text};
+ {error, R} ->
+ {error, file:format_error(R)}
+ end.
+
+uri_get_http(URI) ->
+ %% Try using option full_result=false
+ case catch {ok, http:request(get, {URI,[]}, [],
+ [{full_result, false}])} of
+ {'EXIT', _} ->
+ uri_get_http_r10(URI);
+ Result ->
+ uri_get_http_1(Result, URI)
+ end.
+
+uri_get_http_r10(URI) ->
+ %% Try most general form of request
+ Result = (catch {ok, http:request(get, {URI,[]}, [], [])}),
+ uri_get_http_1(Result, URI).
+
+uri_get_http_1(Result, URI) ->
+ case Result of
+ {ok, {ok, {200, Text}}} when is_list(Text) ->
+ %% new short result format
+ {ok, Text};
+ {ok, {ok, {Status, Text}}} when is_integer(Status), is_list(Text) ->
+ %% new short result format when status /= 200
+ Phrase = httpd_util:reason_phrase(Status),
+ {error, http_errmsg(Phrase, URI)};
+ {ok, {ok, {{_Vsn, 200, _Phrase}, _Hdrs, Text}}} when is_list(Text) ->
+ %% new long result format
+ {ok, Text};
+ {ok, {ok, {{_Vsn, _Status, Phrase}, _Hdrs, Text}}} when is_list(Text) ->
+ %% new long result format when status /= 200
+ {error, http_errmsg(Phrase, URI)};
+ {ok, {200,_Hdrs,Text}} when is_list(Text) ->
+ %% old result format
+ {ok, Text};
+ {ok, {Status,_Hdrs,Text}} when is_list(Text) ->
+ %% old result format when status /= 200
+ Phrase = httpd_util:reason_phrase(Status),
+ {error, http_errmsg(Phrase, URI)};
+ {ok, {error, R}} ->
+ Reason = inet:format_error(R),
+ {error, http_errmsg(Reason, URI)};
+ {ok, R} ->
+ Reason = io_lib:format("bad return value ~P", [R, 5]),
+ {error, http_errmsg(Reason, URI)};
+ {'EXIT', R} ->
+ Reason = io_lib:format("crashed with reason ~w", [R]),
+ {error, http_errmsg(Reason, URI)};
+ R ->
+ Reason = io_lib:format("uncaught throw: ~w", [R]),
+ {error, http_errmsg(Reason, URI)}
+ end.
+
+http_errmsg(Reason, URI) ->
+ io_lib:format("http error: ~s: '~s'", [Reason, URI]).
+
+%% TODO: implement ftp access method
+
+uri_get_ftp(URI) ->
+ Msg = io_lib:format("cannot access ftp scheme yet: '~s'.", [URI]),
+ {error, Msg}.
+
+to_label([$\s | Cs]) ->
+ to_label(Cs);
+to_label([$\t | Cs]) ->
+ to_label(Cs);
+to_label([$\n | Cs]) ->
+ to_label(Cs);
+to_label([]) ->
+ [];
+to_label(Cs) ->
+ to_label_1(Cs).
+
+to_label_1([$\s | Cs]) ->
+ to_label_2([$\s | Cs]);
+to_label_1([$\t | Cs]) ->
+ to_label_2([$\s | Cs]);
+to_label_1([$\n | Cs]) ->
+ to_label_2([$\s | Cs]);
+to_label_1([C | Cs]) ->
+ [C | to_label_1(Cs)];
+to_label_1([]) ->
+ [].
+
+to_label_2(Cs) ->
+ case to_label(Cs) of
+ [] -> [];
+ Cs1 -> [$_ | Cs1]
+ end.
+
+
+%% ---------------------------------------------------------------------
+%% Files
+
+filename([C | T]) when is_integer(C), C > 0 ->
+ [C | filename(T)];
+filename([H|T]) ->
+ filename(H) ++ filename(T);
+filename([]) ->
+ [];
+filename(N) when is_atom(N) ->
+ atom_to_list(N);
+filename(N) ->
+ report("bad filename: `~P'.", [N, 25]),
+ exit(error).
+
+copy_file(From, To) ->
+ case file:copy(From, To) of
+ {ok, _} -> ok;
+ {error, R} ->
+ R1 = file:format_error(R),
+ report("error copying '~s' to '~s': ~s.", [From, To, R1]),
+ exit(error)
+ end.
+
+list_dir(Dir, Error) ->
+ case file:list_dir(Dir) of
+ {ok, Fs} ->
+ Fs;
+ {error, R} ->
+ F = case Error of
+ %% true ->
+ %% fun (S, As) -> report(S, As), exit(error) end;
+ false ->
+ fun (S, As) -> warning(S, As), [] end
+ end,
+ R1 = file:format_error(R),
+ F("could not read directory '~s': ~s.", [filename(Dir), R1])
+ end.
+
+simplify_path(P) ->
+ case filename:basename(P) of
+ "." ->
+ simplify_path(filename:dirname(P));
+ ".." ->
+ simplify_path(filename:dirname(filename:dirname(P)));
+ _ ->
+ P
+ end.
+
+%% The directories From and To are assumed to exist.
+
+%% copy_dir(From, To) ->
+%% Es = list_dir(From, true), % error if listing fails
+%% lists:foreach(fun (E) -> copy_dir(From, To, E) end, Es).
+
+%% copy_dir(From, To, Entry) ->
+%% From1 = filename:join(From, Entry),
+%% To1 = filename:join(To, Entry),
+%% case filelib:is_dir(From1) of
+%% true ->
+%% make_dir(To1),
+%% copy_dir(From1, To1);
+%% false ->
+%% copy_file(From1, To1)
+%% end.
+
+%% make_dir(Dir) ->
+%% case file:make_dir(Dir) of
+%% ok -> ok;
+%% {error, R} ->
+%% R1 = file:format_error(R),
+%% report("cannot create directory '~s': ~s.", [Dir, R1]),
+%% exit(error)
+%% end.
+
+try_subdir(Dir, Subdir) ->
+ D = filename:join(Dir, Subdir),
+ case filelib:is_dir(D) of
+ true -> D;
+ false -> Dir
+ end.
+
+%% @spec (Text::deep_string(), Dir::edoc:filename(),
+%% Name::edoc:filename()) -> ok
+%%
+%% @doc Write the given `Text' to the file named by `Name' in directory
+%% `Dir'. If the target directory does not exist, it will be created.
+
+write_file(Text, Dir, Name) ->
+ write_file(Text, Dir, Name, '').
+
+
+%% @spec (Text::deep_string(), Dir::edoc:filename(),
+%% Name::edoc:filename(), Package::atom()|string()) -> ok
+%% @doc Like {@link write_file/3}, but adds path components to the target
+%% directory corresponding to the specified package.
+
+write_file(Text, Dir, Name, Package) ->
+ Dir1 = filename:join([Dir | packages:split(Package)]),
+ File = filename:join(Dir1, Name),
+ ok = filelib:ensure_dir(File),
+ case file:open(File, [write]) of
+ {ok, FD} ->
+ io:put_chars(FD, Text),
+ ok = file:close(FD);
+ {error, R} ->
+ R1 = file:format_error(R),
+ report("could not write file '~s': ~s.", [File, R1]),
+ exit(error)
+ end.
+
+write_info_file(App, Packages, Modules, Dir) ->
+ Ts = [{packages, Packages},
+ {modules, Modules}],
+ Ts1 = if App =:= ?NO_APP -> Ts;
+ true -> [{application, App} | Ts]
+ end,
+ S = [io_lib:fwrite("~p.\n", [T]) || T <- Ts1],
+ write_file(S, Dir, ?INFO_FILE).
+
+%% @spec (Name::edoc:filename()) -> {ok, string()} | {error, Reason}
+%%
+%% @doc Reads text from the file named by `Name'.
+
+read_file(File) ->
+ case file:read_file(File) of
+ {ok, Bin} -> {ok, binary_to_list(Bin)};
+ {error, Reason} -> {error, Reason}
+ end.
+
+
+%% ---------------------------------------------------------------------
+%% Info files
+
+info_file_data(Ts) ->
+ App = proplists:get_value(application, Ts, ?NO_APP),
+ Ps = proplists:append_values(packages, Ts),
+ Ms = proplists:append_values(modules, Ts),
+ {App, Ps, Ms}.
+
+%% Local file access - don't complain if file does not exist.
+
+read_info_file(Dir) ->
+ File = filename:join(Dir, ?INFO_FILE),
+ case filelib:is_file(File) of
+ true ->
+ case read_file(File) of
+ {ok, Text} ->
+ parse_info_file(Text, File);
+ {error, R} ->
+ R1 = file:format_error(R),
+ warning("could not read '~s': ~s.", [File, R1]),
+ {?NO_APP, [], []}
+ end;
+ false ->
+ {?NO_APP, [], []}
+ end.
+
+%% URI access
+
+uri_get_info_file(Base) ->
+ URI = join_uri(Base, ?INFO_FILE),
+ case uri_get(URI) of
+ {ok, Text} ->
+ parse_info_file(Text, URI);
+ {error, Msg} ->
+ warning("could not read '~s': ~s.", [URI, Msg]),
+ {?NO_APP, [], []}
+ end.
+
+parse_info_file(Text, Name) ->
+ case parse_terms(Text) of
+ {ok, Vs} ->
+ info_file_data(Vs);
+ {error, eof} ->
+ warning("unexpected end of file in '~s'.", [Name]),
+ {?NO_APP, [], []};
+ {error, {_Line,Module,R}} ->
+ warning("~s: ~s.", [Module:format_error(R), Name]),
+ {?NO_APP, [], []}
+ end.
+
+parse_terms(Text) ->
+ case erl_scan:string(Text) of
+ {ok, Ts, _Line} ->
+ parse_terms_1(Ts, [], []);
+ {error, R, _Line} ->
+ {error, R}
+ end.
+
+parse_terms_1([T={dot, _L} | Ts], As, Vs) ->
+ case erl_parse:parse_term(lists:reverse([T | As])) of
+ {ok, V} ->
+ parse_terms_1(Ts, [], [V | Vs]);
+ {error, R} ->
+ {error, R}
+ end;
+parse_terms_1([T | Ts], As, Vs) ->
+ parse_terms_1(Ts, [T | As], Vs);
+parse_terms_1([], [], Vs) ->
+ {ok, lists:reverse(Vs)};
+parse_terms_1([], _As, _Vs) ->
+ {error, eof}.
+
+
+%% ---------------------------------------------------------------------
+%% Source files and packages
+
+find_sources(Path, Opts) ->
+ find_sources(Path, "", Opts).
+
+%% @doc See {@link edoc:run/3} for a description of the options
+%% `subpackages', `source_suffix' and `exclude_packages'.
+
+%% NEW-OPTIONS: subpackages, source_suffix, exclude_packages
+%% DEFER-OPTIONS: edoc:run/3
+
+find_sources(Path, Pkg, Opts) ->
+ Rec = proplists:get_bool(subpackages, Opts),
+ Ext = proplists:get_value(source_suffix, Opts, ?DEFAULT_SOURCE_SUFFIX),
+ find_sources(Path, Pkg, Rec, Ext, Opts).
+
+find_sources(Path, Pkg, Rec, Ext, Opts) ->
+ Skip = proplists:get_value(exclude_packages, Opts, []),
+ lists:flatten(find_sources_1(Path, to_atom(Pkg), Rec, Ext, Skip)).
+
+find_sources_1([P | Ps], Pkg, Rec, Ext, Skip) ->
+ Dir = filename:join(P, filename:join(packages:split(Pkg))),
+ Fs1 = find_sources_1(Ps, Pkg, Rec, Ext, Skip),
+ case filelib:is_dir(Dir) of
+ true ->
+ [find_sources_2(Dir, Pkg, Rec, Ext, Skip) | Fs1];
+ false ->
+ Fs1
+ end;
+find_sources_1([], _Pkg, _Rec, _Ext, _Skip) ->
+ [].
+
+find_sources_2(Dir, Pkg, Rec, Ext, Skip) ->
+ case lists:member(Pkg, Skip) of
+ false ->
+ Es = list_dir(Dir, false), % just warn if listing fails
+ Es1 = [{Pkg, E, Dir} || E <- Es, is_source_file(E, Ext)],
+ case Rec of
+ true ->
+ [find_sources_3(Es, Dir, Pkg, Rec, Ext, Skip) | Es1];
+ false ->
+ Es1
+ end;
+ true ->
+ []
+ end.
+
+find_sources_3(Es, Dir, Pkg, Rec, Ext, Skip) ->
+ [find_sources_2(filename:join(Dir, E),
+ to_atom(packages:concat(Pkg, E)), Rec, Ext, Skip)
+ || E <- Es, is_package_dir(E, Dir)].
+
+is_source_file(Name, Ext) ->
+ (filename:extension(Name) == Ext)
+ andalso is_name(filename:rootname(Name, Ext)).
+
+is_package_dir(Name, Dir) ->
+ is_name(filename:rootname(filename:basename(Name)))
+ andalso filelib:is_dir(filename:join(Dir, Name)).
+
+find_file([P | Ps], Pkg, Name) ->
+ Dir = filename:join(P, filename:join(packages:split(Pkg))),
+ File = filename:join(Dir, Name),
+ case filelib:is_file(File) of
+ true ->
+ File;
+ false ->
+ find_file(Ps, Pkg, Name)
+ end;
+find_file([], _Pkg, _Name) ->
+ "".
+
+find_doc_dirs() ->
+ find_doc_dirs(code:get_path()).
+
+find_doc_dirs([P0 | Ps]) ->
+ P = filename:absname(P0),
+ P1 = case filename:basename(P) of
+ ?EBIN_DIR ->
+ filename:dirname(P);
+ _ ->
+ P
+ end,
+ Dir = try_subdir(P1, ?EDOC_DIR),
+ File = filename:join(Dir, ?INFO_FILE),
+ case filelib:is_file(File) of
+ true ->
+ [Dir | find_doc_dirs(Ps)];
+ false ->
+ find_doc_dirs(Ps)
+ end;
+find_doc_dirs([]) ->
+ [].
+
+%% All names with "internal linkage" are mapped to the empty string, so
+%% that relative references will be created. For apps, the empty string
+%% implies that we use the default app-path.
+
+%% NEW-OPTIONS: doc_path
+%% DEFER-OPTIONS: get_doc_env/4
+
+get_doc_links(App, Packages, Modules, Opts) ->
+ Path = proplists:append_values(doc_path, Opts) ++ find_doc_dirs(),
+ Ds = [{P, uri_get_info_file(P)} || P <- Path],
+ Ds1 = [{"", {App, Packages, Modules}} | Ds],
+ D = dict:new(),
+ make_links(Ds1, D, D, D).
+
+make_links([{Dir, {App, Ps, Ms}} | Ds], A, P, M) ->
+ A1 = if App == ?NO_APP -> A;
+ true -> add_new(App, Dir, A)
+ end,
+ F = fun (K, D) -> add_new(K, Dir, D) end,
+ P1 = lists:foldl(F, P, Ps),
+ M1 = lists:foldl(F, M, Ms),
+ make_links(Ds, A1, P1, M1);
+make_links([], A, P, M) ->
+ F = fun (D) ->
+ fun (K) ->
+ case dict:find(K, D) of
+ {ok, V} -> V;
+ error -> ""
+ end
+ end
+ end,
+ {F(A), F(P), F(M)}.
+
+add_new(K, V, D) ->
+ case dict:is_key(K, D) of
+ true ->
+ D;
+ false ->
+ dict:store(K, V, D)
+ end.
+
+%% @spec (Options::proplist()) -> edoc_env()
+%% @equiv get_doc_env([], [], [], Opts)
+
+get_doc_env(Opts) ->
+ get_doc_env([], [], [], Opts).
+
+%% @spec (App, Packages, Modules, Options::proplist()) -> edoc_env()
+%% App = [] | atom()
+%% Packages = [atom()]
+%% Modules = [atom()]
+%% proplist() = [term()]
+%%
+%% @type edoc_env(). Environment information needed by EDoc for
+%% generating references. The data representation is not documented.
+%%
+%% @doc Creates an environment data structure used by parts of EDoc for
+%% generating references, etc. See {@link edoc:run/3} for a description
+%% of the options `file_suffix', `app_default' and `doc_path'.
+%%
+%% @see edoc_extract:source/4
+%% @see edoc:get_doc/3
+
+%% NEW-OPTIONS: file_suffix, app_default
+%% INHERIT-OPTIONS: get_doc_links/4
+%% DEFER-OPTIONS: edoc:run/3
+
+get_doc_env(App, Packages, Modules, Opts) ->
+ Suffix = proplists:get_value(file_suffix, Opts,
+ ?DEFAULT_FILE_SUFFIX),
+ AppDefault = proplists:get_value(app_default, Opts, ?APP_DEFAULT),
+ Includes = proplists:append_values(includes, Opts),
+
+ {A, P, M} = get_doc_links(App, Packages, Modules, Opts),
+ #env{file_suffix = Suffix,
+ package_summary = ?PACKAGE_SUMMARY ++ Suffix,
+ apps = A,
+ packages = P,
+ modules = M,
+ app_default = AppDefault,
+ includes = Includes
+ }.
+
+%% ---------------------------------------------------------------------
+%% Plug-in modules
+
+%% @doc See {@link edoc:run/3} for a description of the `doclet' option.
+
+%% NEW-OPTIONS: doclet
+%% DEFER-OPTIONS: edoc:run/3
+
+run_doclet(Fun, Opts) ->
+ run_plugin(doclet, ?DEFAULT_DOCLET, Fun, Opts).
+
+%% @doc See {@link edoc:layout/2} for a description of the `layout'
+%% option.
+
+%% NEW-OPTIONS: layout
+%% DEFER-OPTIONS: edoc:layout/2
+
+run_layout(Fun, Opts) ->
+ run_plugin(layout, ?DEFAULT_LAYOUT, Fun, Opts).
+
+run_plugin(Name, Default, Fun, Opts) ->
+ run_plugin(Name, Name, Default, Fun, Opts).
+
+run_plugin(Name, Key, Default, Fun, Opts) when is_atom(Name) ->
+ Module = get_plugin(Key, Default, Opts),
+ case catch {ok, Fun(Module)} of
+ {ok, Value} ->
+ Value;
+ R ->
+ report("error in ~s '~w': ~W.", [Name, Module, R, 20]),
+ exit(error)
+ end.
+
+get_plugin(Key, Default, Opts) ->
+ case proplists:get_value(Key, Opts, Default) of
+ M when is_atom(M) ->
+ M;
+ Other ->
+ report("bad value for option '~w': ~P.", [Key, Other, 10]),
+ exit(error)
+ end.
+
+
+%% ---------------------------------------------------------------------
+%% Error handling
+
+throw_error({missing, C}, L) ->
+ throw_error({"missing '~c'.", [C]}, L);
+throw_error(eof, L) ->
+ throw({error,L,"unexpected end of expression."});
+throw_error({L, M, D}, _L) ->
+ throw({error,L,{format_error,M,D}});
+throw_error(D, L) ->
+ throw({error, L, D}).
diff --git a/lib/edoc/src/edoc_macros.erl b/lib/edoc/src/edoc_macros.erl
new file mode 100644
index 0000000000..2874e2940c
--- /dev/null
+++ b/lib/edoc/src/edoc_macros.erl
@@ -0,0 +1,327 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @private
+%% @copyright 2001-2005 Richard Carlsson
+%% @author Richard Carlsson <[email protected]>
+%% @see edoc
+%% @end
+%% =====================================================================
+
+%% @doc EDoc macro expansion
+
+-module(edoc_macros).
+
+-export([expand_tags/3, std_macros/1, check_defs/1]).
+
+-import(edoc_report, [report/2, error/3, warning/4]).
+
+-include("edoc.hrl").
+-include("edoc_types.hrl").
+
+-define(DEFAULT_XML_EXPORT, xmerl_html).
+
+
+std_macros(Env) ->
+ (if Env#env.module =:= [] -> [];
+ true -> [{module, atom_to_list(Env#env.module)}]
+ end
+ ++
+ if Env#env.package =:= [] -> [];
+ true -> [{package, atom_to_list(Env#env.package)}]
+ end
+ ++
+ [{date, fun date_macro/3},
+ {docRoot, Env#env.root},
+ {link, fun link_macro/3},
+ {section, fun section_macro/3},
+ {time, fun time_macro/3},
+ {type, fun type_macro/3},
+ {version, fun version_macro/3}]).
+
+
+%% Check well-formedness of user-specified list of macro definitions.
+
+check_defs([{K, D} | Ds]) when is_atom(K), is_list(D) ->
+ check_defs(Ds);
+check_defs([X | _Ds]) ->
+ report("bad macro definition: ~P.", [X, 10]),
+ exit(error);
+check_defs([]) ->
+ ok.
+
+%% Code for special macros should throw {error, Line, Reason} for error
+%% reporting, where Reason and Line are passed to edoc_report:error(...)
+%% together with the file name etc. The expanded text must be flat!
+
+date_macro(_S, _Line, _Env) ->
+ edoc_lib:datestr(date()).
+
+time_macro(_S, _Line, _Env) ->
+ edoc_lib:timestr(time()).
+
+version_macro(S, Line, Env) ->
+ date_macro(S, Line, Env)
+ ++ " " ++ time_macro(S, Line, Env).
+
+link_macro(S, Line, Env) ->
+ {S1, S2} = edoc_lib:split_at_stop(S),
+ Ref = edoc_parser:parse_ref(S1, Line),
+ URI = edoc_refs:get_uri(Ref, Env),
+ Txt = if S2 =:= [] -> "<code>" ++ S1 ++ "</code>";
+ true -> S2
+ end,
+ Target = case edoc_refs:is_top(Ref, Env) of
+ true -> " target=\"_top\""; % note the initial space
+ false -> ""
+ end,
+ lists:flatten(io_lib:fwrite("<a href=\"~s\"~s>~s</a>",
+ [URI, Target, Txt])).
+
+section_macro(S, _Line, _Env) ->
+ S1 = lists:reverse(edoc_lib:strip_space(
+ lists:reverse(edoc_lib:strip_space(S)))),
+ lists:flatten(io_lib:format("<a href=\"#~s\">~s</a>",
+ [edoc_lib:to_label(S1), S1])).
+
+type_macro(S, Line, Env) ->
+ S1 = "t()=" ++ S,
+ Def = edoc_parser:parse_typedef(S1, Line),
+ {#t_typedef{type = T}, _} = Def,
+ Txt = edoc_layout:type(edoc_data:type(T, Env)),
+ lists:flatten(io_lib:fwrite("<code>~s</code>", [Txt])).
+
+
+%% Expand inline macros in tag content.
+
+expand_tags(Ts, Env, Where) ->
+ Defs = dict:from_list(lists:reverse(Env#env.macros)),
+ expand_tags(Ts, Defs, Env, Where).
+
+expand_tags([#tag{data = Cs, line = L} = T | Ts], Defs, Env, Where) ->
+ [T#tag{data = expand_tag(Cs, L, Defs, Env, Where)}
+ | expand_tags(Ts, Defs, Env, Where)];
+expand_tags([T | Ts], Defs, Env, Where) ->
+ [T | expand_tags(Ts, Defs, Env, Where)];
+expand_tags([], _, _, _) ->
+ [].
+
+expand_tag(Cs, L, Defs, Env, Where) ->
+ case catch {ok, expand_text(Cs, L, Defs, Env, Where)} of
+ {ok, Cs1} ->
+ lists:reverse(Cs1);
+ {'EXIT', R} ->
+ exit(R);
+ {error, L1, Error} ->
+ error(L1, Where, Error),
+ exit(error);
+ Other ->
+ throw(Other)
+ end.
+
+%% Expand macros in arbitrary lines of text.
+%% The result is in reverse order.
+
+-record(state, {where, env, seen}).
+
+expand_text(Cs, L, Defs, Env, Where) ->
+ St = #state{where = Where,
+ env = Env,
+ seen = sets:new()},
+ expand(Cs, L, Defs, St, []).
+
+%% Inline macro syntax: "{@name content}"
+%% where 'content' is optional, and separated from 'name' by one or
+%% more whitespace characters. The content is bound to the '{@?}'
+%% parameter variable, and the macro definition is expanded and
+%% substituted for the call. Recursion is detected and reported as an
+%% error, since there are (currently) no control flow operators.
+%% Escape sequences:
+%% "@{" -> "{"
+%% "@}" -> "}"
+%% "@@" -> "@"
+
+expand([$@, $@ | Cs], L, Defs, St, As) ->
+ expand(Cs, L, Defs, St, [$@ | As]);
+expand([$@, ${ | Cs], L, Defs, St, As) ->
+ expand(Cs, L, Defs, St, [${ | As]);
+expand([$@, $} | Cs], L, Defs, St, As) ->
+ expand(Cs, L, Defs, St, [$} | As]);
+expand([${, $@ | Cs], L, Defs, St, As) ->
+ expand_macro(Cs, L, Defs, St, As);
+expand([$\n = C | Cs], L, Defs, St, As) ->
+ expand(Cs, L + 1, Defs, St, [C | As]);
+expand([C | Cs], L, Defs, St, As) ->
+ expand(Cs, L, Defs, St, [C | As]);
+expand([], _, _, _, As) ->
+ As.
+
+expand_macro(Cs, L, Defs, St, As) ->
+ {M, Cs1, L1} = macro_name(Cs, L),
+ {Arg, Cs2, L2} = macro_content(Cs1, L1),
+ As1 = expand_macro_def(M, Arg, L, Defs, St, As),
+ expand(Cs2, L2, Defs, St, As1).
+
+%% The macro argument (the "content") is expanded in the environment of
+%% the call, and the result is bound to the '{@?}' parameter. The result
+%% of the macro expansion is then expanded again. This allows macro
+%% definitions to contain calls to other macros, avoids name capture of
+%% '{@?}', and makes it easier to write handler functions for special
+%% macros such as '{@link ...}', since the argument is already expanded.
+
+expand_macro_def(M, Arg, L, Defs, St, As) ->
+ Seen = St#state.seen,
+ case sets:is_element(M, Seen) of
+ true ->
+ throw_error(L, {"recursive macro expansion of {@~s}.",
+ [M]});
+ false ->
+ Arg1 = lists:reverse(expand(Arg, L, Defs, St, [])),
+ Defs1 = dict:store('?', Arg1, Defs),
+ St1 = St#state{seen = sets:add_element(M, Seen)},
+ case dict:find(M, Defs) of
+ {ok, Def} ->
+ Txt = if is_function(Def) ->
+ Def(Arg1, L, St1#state.env);
+ is_list(Def) ->
+ Def
+ end,
+ expand(Txt, L, Defs1, St1, As);
+ error ->
+ warning(L, St1#state.where,
+ "undefined macro {@~s}.", [M]),
+ "??"
+ end
+ end.
+
+%% The macro name ends at the first whitespace or '}' character. The
+%% content, if any, starts at the next non-whitespace character.
+
+%% See edoc_tags:scan_tag/is_name/1 for details on what is a valid
+%% name. In macro names we also allow '?' as the initial character.
+
+macro_name(Cs, L) ->
+ macro_name(Cs, [], L).
+
+macro_name([C | Cs], As, L) when C >= $a, C =< $z ->
+ macro_name_1(Cs, [C | As], L);
+macro_name([C | Cs], As, L) when C >= $A, C =< $Z ->
+ macro_name_1(Cs, [C | As], L);
+macro_name([C | Cs], As, L) when C >= $\300, C =< $\377,
+ C =/= $\327, C =/= $\367 ->
+ macro_name_1(Cs, [C | As], L);
+macro_name([$_ | Cs], As, L) ->
+ macro_name_1(Cs, [$_ | As], L);
+macro_name([$? | Cs], As, L) ->
+ macro_name_1(Cs, [$? | As], L);
+macro_name([$\s | _Cs], _As, L) ->
+ throw_error(L, macro_name);
+macro_name([$\t | _Cs], _As, L) ->
+ throw_error(L, macro_name);
+macro_name([$\n | _Cs], _As, L) ->
+ throw_error(L, macro_name);
+macro_name([C | _Cs], As, L) ->
+ throw_error(L, {macro_name, [C | As]});
+macro_name([], _As, L) ->
+ throw_error(L, macro_name).
+
+macro_name_1([C | Cs], As, L) when C >= $a, C =< $z ->
+ macro_name_1(Cs, [C | As], L);
+macro_name_1([C | Cs], As, L) when C >= $A, C =< $Z ->
+ macro_name_1(Cs, [C | As], L);
+macro_name_1([C | Cs], As, L) when C >= $0, C =< $9 ->
+ macro_name_1(Cs, [C | As], L);
+macro_name_1([C | Cs], As, L) when C >= $\300, C =< $\377,
+ C =/= $\327, C =/= $\367 ->
+ macro_name_1(Cs, [C | As], L);
+macro_name_1([$_ | Cs], As, L) ->
+ macro_name_1(Cs, [$_ | As], L);
+macro_name_1([$\s | Cs], As, L) ->
+ macro_name_2(Cs, As, L);
+macro_name_1([$\t | Cs], As, L) ->
+ macro_name_2(Cs, As, L);
+macro_name_1([$\n | Cs], As, L) ->
+ macro_name_2(Cs, As, L + 1);
+macro_name_1([$} | _] = Cs, As, L) ->
+ macro_name_3(Cs, As, L);
+macro_name_1([C | _Cs], As, L) ->
+ throw_error(L, {macro_name, [C | As]});
+macro_name_1([], _As, L) ->
+ throw_error(L, unterminated_macro).
+
+macro_name_2([$\s | Cs], As, L) ->
+ macro_name_2(Cs, As, L);
+macro_name_2([$\t | Cs], As, L) ->
+ macro_name_2(Cs, As, L);
+macro_name_2([$\n | Cs], As, L) ->
+ macro_name_2(Cs, As, L + 1);
+macro_name_2([_ | _] = Cs, As, L) ->
+ macro_name_3(Cs, As, L);
+macro_name_2([], _As, L) ->
+ throw_error(L, unterminated_macro).
+
+macro_name_3(Cs, As, L) ->
+ {list_to_atom(lists:reverse(As)), Cs, L}.
+
+
+%% The macro content ends at the first non-escaped '}' character that is
+%% not balanced by a corresponding non-escaped '{@' sequence.
+%% Escape sequences are those defined above.
+
+macro_content(Cs, L) ->
+ %% If there is an error, we report the start line, not the end line.
+ case catch {ok, macro_content(Cs, [], L, 0)} of
+ {ok, X} ->
+ X;
+ {'EXIT', R} ->
+ exit(R);
+ 'end' ->
+ throw_error(L, unterminated_macro);
+ Other ->
+ throw(Other)
+ end.
+
+%% @throws 'end'
+
+macro_content([$@, $@ | Cs], As, L, N) ->
+ macro_content(Cs, [$@, $@ | As], L, N); % escaped '@'
+macro_content([$@, $} | Cs], As, L, N) ->
+ macro_content(Cs, [$}, $@ | As], L, N); % escaped '}'
+macro_content([$@, ${ | Cs], As, L, N) ->
+ macro_content(Cs, [${, $@ | As], L, N); % escaped '{'
+macro_content([${, $@ | Cs], As, L, N) ->
+ macro_content(Cs, [$@, ${ | As], L, N + 1);
+macro_content([$} | Cs], As, L, 0) ->
+ {lists:reverse(As), Cs, L};
+macro_content([$} | Cs], As, L, N) ->
+ macro_content(Cs, [$} | As], L, N - 1);
+macro_content([$\n = C | Cs], As, L, N) ->
+ macro_content(Cs, [C | As], L + 1, N);
+macro_content([C | Cs], As, L, N) ->
+ macro_content(Cs, [C | As], L, N);
+macro_content([], _As, _L, _N) ->
+ throw('end').
+
+throw_error(L, unterminated_macro) ->
+ throw_error(L, {"unexpected end of macro.", []});
+throw_error(L, macro_name) ->
+ throw_error(L, {"missing macro name.", []});
+throw_error(L, {macro_name, S}) ->
+ throw_error(L, {"bad macro name: '@~s...'.", [lists:reverse(S)]});
+throw_error(L, D) ->
+ throw({error, L, D}).
diff --git a/lib/edoc/src/edoc_parser.yrl b/lib/edoc/src/edoc_parser.yrl
new file mode 100644
index 0000000000..0eea8ae66f
--- /dev/null
+++ b/lib/edoc/src/edoc_parser.yrl
@@ -0,0 +1,423 @@
+%% ========================== -*-Erlang-*- =============================
+%% EDoc type specification grammar for the Yecc parser generator,
+%% adapted from Sven-Olof Nystr�m's type specification parser.
+%%
+%% Also contains entry points for parsing things like typedefs,
+%% references, and throws-declarations.
+%%
+%% Copyright (C) 2002-2005 Richard Carlsson
+%%
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% Author contact: [email protected]
+%%
+%% $Id$
+%%
+%% =====================================================================
+
+Nonterminals
+start spec func_type utype_list utype_tuple utypes utype ptypes ptype
+nutype function_name where_defs defs def typedef etype throws qname ref
+aref mref lref pref var_list vars fields field.
+
+Terminals
+atom float integer var string start_spec start_typedef start_throws
+start_ref
+
+'(' ')' ',' '.' '->' '{' '}' '[' ']' '|' '+' ':' '::' '=' '/' '//' '*'
+'#' 'where'.
+
+Rootsymbol start.
+
+start -> start_spec spec: '$2'.
+start -> start_throws throws: '$2'.
+start -> start_typedef typedef: '$2'.
+start -> start_ref ref: '$2'.
+
+%% Produced in reverse order.
+qname -> atom: [tok_val('$1')].
+qname -> qname '.' atom: [tok_val('$3') | '$1'].
+
+spec -> func_type where_defs:
+ #t_spec{type = '$1', defs = lists:reverse('$2')}.
+spec -> function_name func_type where_defs:
+ #t_spec{name = '$1', type = '$2', defs = lists:reverse('$3')}.
+
+where_defs -> 'where' defs: '$2'.
+where_defs -> defs: '$1'.
+
+function_name -> atom: #t_name{name = tok_val('$1')}.
+
+func_type -> utype_list '->' utype:
+ #t_fun{args = element(1, '$1'), range = '$3'}.
+
+
+%% Paired with line number, for later error reporting
+utype_list -> '(' ')' : {[], tok_line('$1')}.
+utype_list -> '(' utypes ')' : {lists:reverse('$2'), tok_line('$1')}.
+
+utype_tuple -> '{' '}' : [].
+utype_tuple -> '{' utypes '}' : lists:reverse('$2').
+
+%% Produced in reverse order.
+utypes -> utype : ['$1'].
+utypes -> utypes ',' utype : ['$3' | '$1'].
+
+utype -> nutype string: annotate('$1', tok_val('$2')).
+utype -> nutype: '$1'.
+
+nutype -> var '::' ptypes: annotate(union('$3'), tok_val('$1')).
+nutype -> ptypes: union('$1').
+
+%% Produced in reverse order.
+ptypes -> ptype : ['$1'].
+ptypes -> ptypes '+' ptype : ['$3' | '$1'].
+ptypes -> ptypes '|' ptype : ['$3' | '$1'].
+
+ptype -> var : #t_var{name = tok_val('$1')}.
+ptype -> atom : #t_atom{val = tok_val('$1')}.
+ptype -> integer: #t_integer{val = tok_val('$1')}.
+ptype -> float: #t_float{val = tok_val('$1')}.
+ptype -> utype_tuple : #t_tuple{types = '$1'}.
+ptype -> '[' ']' : #t_nil{}.
+ptype -> '[' utype ']' : #t_list{type = '$2'}.
+ptype -> utype_list:
+ if length(element(1, '$1')) == 1 ->
+ %% there must be exactly one utype in the list
+ hd(element(1, '$1'));
+ length(element(1, '$1')) == 0 ->
+ return_error(element(2, '$1'), "syntax error before: ')'");
+ true ->
+ return_error(element(2, '$1'), "syntax error before: ','")
+ end.
+ptype -> utype_list '->' ptype:
+ #t_fun{args = element(1, '$1'), range = '$3'}.
+ptype -> '#' atom '{' '}' :
+ #t_record{name = #t_atom{val = tok_val('$2')}}.
+ptype -> '#' atom '{' fields '}' :
+ #t_record{name = #t_atom{val = tok_val('$2')},
+ fields = lists:reverse('$4')}.
+ptype -> atom utype_list:
+ #t_type{name = #t_name{name = tok_val('$1')},
+ args = element(1, '$2')}.
+ptype -> qname ':' atom utype_list :
+ #t_type{name = #t_name{module = qname('$1'),
+ name = tok_val('$3')},
+ args = element(1, '$4')}.
+ptype -> '//' atom '/' qname ':' atom utype_list :
+ #t_type{name = #t_name{app = tok_val('$2'),
+ module = qname('$4'),
+ name = tok_val('$6')},
+ args = element(1, '$7')}.
+
+%% Produced in reverse order.
+fields -> field : ['$1'].
+fields -> fields ',' field : ['$3' | '$1'].
+
+field -> atom '=' utype :
+ #t_field{name = #t_atom{val = tok_val('$1')}, type = '$3'}.
+
+%% Produced in reverse order.
+defs -> '$empty' : [].
+defs -> defs def : ['$2' | '$1'].
+defs -> defs ',' def : ['$3' | '$1'].
+
+def -> var '=' utype:
+ #t_def{name = #t_var{name = tok_val('$1')},
+ type = '$3'}.
+def -> atom var_list '=' utype:
+ #t_def{name = #t_type{name = #t_name{name = tok_val('$1')},
+ args = '$2'},
+ type = '$4'}.
+
+var_list -> '(' ')' : [].
+var_list -> '(' vars ')' : lists:reverse('$2').
+
+%% Produced in reverse order.
+vars -> var : [#t_var{name = tok_val('$1')}].
+vars -> vars ',' var : [#t_var{name = tok_val('$3')} | '$1'].
+
+typedef -> atom var_list where_defs:
+ #t_typedef{name = #t_name{name = tok_val('$1')},
+ args = '$2',
+ defs = lists:reverse('$3')}.
+typedef -> atom var_list '=' utype where_defs:
+ #t_typedef{name = #t_name{name = tok_val('$1')},
+ args = '$2',
+ type = '$4',
+ defs = lists:reverse('$5')}.
+
+%% References
+
+ref -> aref: '$1'.
+ref -> mref: '$1'.
+ref -> lref: '$1'.
+ref -> pref: '$1'.
+
+aref -> '//' atom:
+ edoc_refs:app(tok_val('$2')).
+aref -> '//' atom '/' mref:
+ edoc_refs:app(tok_val('$2'), '$4').
+aref -> '//' atom '/' pref:
+ edoc_refs:app(tok_val('$2'), '$4').
+
+mref -> qname ':' atom '/' integer:
+ edoc_refs:function(qname('$1'), tok_val('$3'), tok_val('$5')).
+mref -> qname ':' atom '(' ')':
+ edoc_refs:type(qname('$1'), tok_val('$3')).
+mref -> qname:
+ edoc_refs:module(qname('$1')).
+
+pref -> qname '.' '*':
+ edoc_refs:package(qname('$1')).
+
+lref -> atom '/' integer:
+ edoc_refs:function(tok_val('$1'), tok_val('$3')).
+lref -> atom '(' ')':
+ edoc_refs:type(tok_val('$1')).
+
+%% Exception declarations
+
+etype -> utype: '$1'.
+
+throws -> etype where_defs:
+ #t_throws{type = '$1',
+ defs = lists:reverse('$2')}.
+
+%% (commented out for now)
+%% Header
+%% "%% ========================== -*-Erlang-*- ============================="
+%% "%% EDoc function specification parser, generated from the file"
+%% "%% \"edoc_parser.yrl\" by the Yecc parser generator."
+%% "%%"
+%% "%% Copyright (C) 2002-2005 Richard Carlsson"
+%% "%%"
+%% "%% This library is free software; you can redistribute it and/or modify"
+%% "%% it under the terms of the GNU Lesser General Public License as"
+%% "%% published by the Free Software Foundation; either version 2 of the"
+%% "%% License, or (at your option) any later version."
+%% "%%"
+%% "%% This library is distributed in the hope that it will be useful, but"
+%% "%% WITHOUT ANY WARRANTY; without even the implied warranty of"
+%% "%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU"
+%% "%% Lesser General Public License for more details."
+%% "%%"
+%% "%% You should have received a copy of the GNU Lesser General Public"
+%% "%% License along with this library; if not, write to the Free Software"
+%% "%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307"
+%% "%% USA"
+%% "%%"
+%% "%% @private"
+%% "%% @author Richard Carlsson <[email protected]>"
+%% "%% ===================================================================="
+%% .
+
+Erlang code.
+
+%% ========================== -*-Erlang-*- =============================
+%% EDoc function specification parser, generated from the file
+%% "edoc_parser.yrl" by the Yecc parser generator.
+%%
+%% Copyright (C) 2002-2005 Richard Carlsson
+%%
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%% ====================================================================
+
+-export([parse_spec/2, parse_typedef/2, parse_throws/2, parse_ref/2,
+ parse_see/2, parse_param/2]).
+
+-include("edoc_types.hrl").
+
+%% Multiple entry point hack:
+
+start_spec(Ts, L) -> run_parser(Ts, L, start_spec).
+
+start_typedef(Ts, L) -> run_parser(Ts, L, start_typedef).
+
+start_throws(Ts, L) -> run_parser(Ts, L, start_throws).
+
+start_ref(Ts, L) -> run_parser(Ts, L, start_ref).
+
+%% Error reporting fix
+
+run_parser(Ts, L, Start) ->
+ case parse([{Start,L} | Ts]) of
+ {error, {999999,?MODULE,_}} ->
+ What = case Start of
+ start_spec -> "specification";
+ start_typedef -> "type definition";
+ start_throws -> "exception declaration";
+ start_ref -> "reference"
+ end,
+ {error, {L,?MODULE,["unexpected end of ", What]}};
+ Other -> Other
+ end.
+
+%% Utility functions:
+
+tok_val(T) -> element(3, T).
+
+tok_line(T) -> element(2, T).
+
+qname([A]) ->
+ A; % avoid unnecessary call to packages:concat/1.
+qname(List) ->
+ list_to_atom(packages:concat(lists:reverse(List))).
+
+union(Ts) ->
+ case Ts of
+ [T] -> T;
+ _ -> #t_union{types = lists:reverse(Ts)}
+ end.
+
+annotate(T, A) -> ?add_t_ann(T, A).
+
+%% ---------------------------------------------------------------------
+
+%% @doc EDoc type specification parsing. Parses the content of
+%% <a href="overview-summary.html#ftag-spec">`@spec'</a> declarations.
+
+parse_spec(S, L) ->
+ case edoc_scanner:string(S, L) of
+ {ok, Ts, _} ->
+ case start_spec(Ts, L) of
+ {ok, Spec} ->
+ Spec;
+ {error, E} ->
+ throw_error(E, L)
+ end;
+ {error, E, _} ->
+ throw_error(E, L)
+ end.
+
+%% ---------------------------------------------------------------------
+
+%% @doc EDoc type definition parsing. Parses the content of
+%% <a href="overview-summary.html#gtag-type">`@type'</a> declarations.
+
+parse_typedef(S, L) ->
+ {S1, S2} = edoc_lib:split_at_stop(S),
+ N = edoc_lib:count($\n, S1),
+ L1 = L + N,
+ Text = edoc_lib:strip_space(S2),
+ {parse_typedef_1(S1, L), edoc_wiki:parse_xml(Text, L1)}.
+
+parse_typedef_1(S, L) ->
+ case edoc_scanner:string(S, L) of
+ {ok, Ts, _} ->
+ case start_typedef(Ts, L) of
+ {ok, T} ->
+ T;
+ {error, E} ->
+ throw_error({parse_typedef, E}, L)
+ end;
+ {error, E, _} ->
+ throw_error({parse_typedef, E}, L)
+ end.
+
+%% ---------------------------------------------------------------------
+
+%% @doc Parses a <a
+%% href="overview-summary.html#References">reference</a> to a module,
+%% package, function, type, or application
+
+parse_ref(S, L) ->
+ case edoc_scanner:string(S, L) of
+ {ok, Ts, _} ->
+ case start_ref(Ts, L) of
+ {ok, T} ->
+ T;
+ {error, E} ->
+ throw_error({parse_ref, E}, L)
+ end;
+ {error, E, _} ->
+ throw_error({parse_ref, E}, L)
+ end.
+
+%% ---------------------------------------------------------------------
+
+%% @doc Parses the content of
+%% <a href="overview-summary.html#ftag-see">`@see'</a> references.
+parse_see(S, L) ->
+ {S1, S2} = edoc_lib:split_at_stop(S),
+ N = edoc_lib:count($\n, S1),
+ L1 = L + N,
+ Text = edoc_lib:strip_space(S2),
+ {parse_ref(S1, L), edoc_wiki:parse_xml(Text, L1)}.
+
+%% ---------------------------------------------------------------------
+
+%% @doc Parses the content of
+%% <a href="overview-summary.html#ftag-param">`@param'</a> tags.
+parse_param(S, L) ->
+ {S1, S2} = edoc_lib:split_at_space(edoc_lib:strip_space(S)),
+ case edoc_lib:strip_space(S1) of
+ "" -> throw_error(parse_param, L);
+ Name ->
+ Text = edoc_lib:strip_space(S2),
+ {list_to_atom(Name), edoc_wiki:parse_xml(Text, L)}
+ end.
+
+%% ---------------------------------------------------------------------
+
+%% @doc EDoc exception specification parsing. Parses the content of
+%% <a href="overview-summary.html#ftag-throws">`@throws'</a> declarations.
+
+parse_throws(S, L) ->
+ case edoc_scanner:string(S, L) of
+ {ok, Ts, _} ->
+ case start_throws(Ts, L) of
+ {ok, Spec} ->
+ Spec;
+ {error, E} ->
+ throw_error({parse_throws, E}, L)
+ end;
+ {error, E, _} ->
+ throw_error({parse_throws, E}, L)
+ end.
+
+%% ---------------------------------------------------------------------
+
+throw_error({L, M, D}, _L0) ->
+ throw({error,L,{format_error,M,D}});
+throw_error({parse_spec, E}, L) ->
+ throw_error({"specification", E}, L);
+throw_error({parse_typedef, E}, L) ->
+ throw_error({"type definition", E}, L);
+throw_error({parse_ref, E}, L) ->
+ throw_error({"reference", E}, L);
+throw_error({parse_throws, E}, L) ->
+ throw_error({"throws-declaration", E}, L);
+throw_error(parse_param, L) ->
+ throw({error, L, "missing parameter name"});
+throw_error({Where, E}, L) when is_list(Where) ->
+ throw({error,L,{"unknown error parsing ~s: ~P.",[Where,E,15]}});
+throw_error(E, L) ->
+ %% Just in case.
+ throw({error,L,{"unknown parse error: ~P.",[E,15]}}).
diff --git a/lib/edoc/src/edoc_refs.erl b/lib/edoc/src/edoc_refs.erl
new file mode 100644
index 0000000000..c2146bbe02
--- /dev/null
+++ b/lib/edoc/src/edoc_refs.erl
@@ -0,0 +1,217 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @private
+%% @copyright 2003 Richard Carlsson
+%% @author Richard Carlsson <[email protected]>
+%% @see edoc
+%% @see edoc_parse_ref
+%% @end
+%% =====================================================================
+
+%% @doc Representation and handling of EDoc object references. See
+%% {@link edoc_parse_ref} for more details.
+
+-module(edoc_refs).
+
+-export([app/1, app/2, package/1, module/1, module/2, module/3,
+ function/2, function/3, function/4, type/1, type/2, type/3,
+ to_string/1, to_label/1, get_uri/2, is_top/2,
+ relative_module_path/2, relative_package_path/2]).
+
+-import(edoc_lib, [join_uri/2, escape_uri/1]).
+
+-include("edoc.hrl").
+
+-define(INDEX_FILE, "index.html").
+
+
+%% Creating references:
+
+app(App) ->
+ {app, App}.
+
+app(App, Ref) ->
+ {app, App, Ref}.
+
+module(M) ->
+ {module, M}.
+
+module(M, Ref) ->
+ {module, M, Ref}.
+
+module(App, M, Ref) ->
+ app(App, module(M, Ref)).
+
+package(P) ->
+ {package, P}.
+
+function(F, A) ->
+ {function, F, A}.
+
+function(M, F, A) ->
+ module(M, function(F, A)).
+
+function(App, M, F, A) ->
+ module(App, M, function(F, A)).
+
+type(T) ->
+ {type, T}.
+
+type(M, T) ->
+ module(M, type(T)).
+
+type(App, M, T) ->
+ module(App, M, type(T)).
+
+
+%% Creating a print string for a reference
+
+to_string({app, A}) ->
+ "//" ++ atom_to_list(A);
+to_string({app, A, Ref}) ->
+ "//" ++ atom_to_list(A) ++ "/" ++ to_string(Ref);
+to_string({module, M}) ->
+ atom_to_list(M) ;
+to_string({module, M, Ref}) ->
+ atom_to_list(M) ++ ":" ++ to_string(Ref);
+to_string({package, P}) ->
+ atom_to_list(P) ++ ".*";
+to_string({function, F, A}) ->
+ atom_to_list(F) ++ "/" ++ integer_to_list(A);
+to_string({type, T}) ->
+ atom_to_list(T) ++ "()".
+
+
+%% Creating URIs and anchors.
+
+to_label({function, F, A}) ->
+ escape_uri(atom_to_list(F)) ++ "-" ++ integer_to_list(A);
+to_label({type, T}) ->
+ "type-" ++ escape_uri(atom_to_list(T)).
+
+get_uri({app, App}, Env) ->
+ join_uri(app_ref(App, Env), ?INDEX_FILE);
+get_uri({app, App, Ref}, Env) ->
+ app_ref(App, Ref, Env);
+get_uri({module, M, Ref}, Env) ->
+ module_ref(M, Env) ++ "#" ++ to_label(Ref);
+get_uri({module, M}, Env) ->
+ module_ref(M, Env);
+get_uri({package, P}, Env) ->
+ package_ref(P, Env);
+get_uri(Ref, _Env) ->
+ "#" ++ to_label(Ref).
+
+abs_uri({module, M}, Env) ->
+ module_absref(M, Env);
+abs_uri({module, M, Ref}, Env) ->
+ module_absref(M, Env) ++ "#" ++ to_label(Ref);
+abs_uri({package, P}, Env) ->
+ package_absref(P, Env).
+
+module_ref(M, Env) ->
+ case (Env#env.modules)(M) of
+ "" ->
+ File = packages:last(M) ++ Env#env.file_suffix,
+ Path = relative_module_path(M, Env#env.package),
+ join_uri(Path, escape_uri(File));
+ Base ->
+ join_uri(Base, module_absref(M, Env))
+ end.
+
+module_absref(M, Env) ->
+ join_segments(packages:split(M))
+ ++ escape_uri(Env#env.file_suffix).
+
+package_ref(P, Env) ->
+ case (Env#env.packages)(P) of
+ "" ->
+ join_uri(relative_package_path(P, Env#env.package),
+ escape_uri(Env#env.package_summary));
+ Base ->
+ join_uri(Base, package_absref(P, Env))
+ end.
+
+package_absref(P, Env) ->
+ join_uri(join_segments(packages:split(P)),
+ escape_uri(Env#env.package_summary)).
+
+app_ref(A, Env) ->
+ case (Env#env.apps)(A) of
+ "" ->
+ join_uri(Env#env.app_default,
+ join_uri(escape_uri(atom_to_list(A)), ?EDOC_DIR));
+ Base ->
+ Base
+ end.
+
+app_ref(A, Ref, Env) ->
+ join_uri(app_ref(A, Env), abs_uri(Ref, Env)).
+
+is_top({app, _App}, _Env) ->
+ true;
+is_top(_Ref, _Env) ->
+ false.
+
+%% Each segment of a path must be separately escaped before joining.
+
+join_segments([S]) ->
+ escape_uri(S);
+join_segments([S | Ss]) ->
+ join_uri(escape_uri(S), join_segments(Ss)).
+
+%% 'From' is always the "current package" here:
+
+%% The empty string is returned if the To module has only one segment,
+%% implying a local reference.
+
+relative_module_path(To, From) ->
+ case first(packages:split(To)) of
+ [] -> "";
+ P -> relative_path(P, packages:split(From))
+ end.
+
+relative_package_path(To, From) ->
+ relative_path(packages:split(To), packages:split(From)).
+
+%% This takes two lists of path segments (From, To). Note that an empty
+%% string will be returned if the paths are the same. Empty leading
+%% segments are stripped from both paths.
+
+relative_path(Ts, ["" | Fs]) ->
+ relative_path(Ts, Fs);
+relative_path(["" | Ts], Fs) ->
+ relative_path(Ts, Fs);
+relative_path(Ts, Fs) ->
+ relative_path_1(Ts, Fs).
+
+relative_path_1([T | Ts], [F | Fs]) when F == T ->
+ relative_path_1(Ts, Fs);
+relative_path_1(Ts, Fs) ->
+ relative_path_2(Fs, Ts).
+
+relative_path_2([_F | Fs], Ts) ->
+ relative_path_2(Fs, [".." | Ts]);
+relative_path_2([], []) ->
+ "";
+relative_path_2([], Ts) ->
+ join_segments(Ts).
+
+first([H | T]) when T /= [] -> [H | first(T)];
+first(_) -> [].
diff --git a/lib/edoc/src/edoc_report.erl b/lib/edoc/src/edoc_report.erl
new file mode 100644
index 0000000000..b87c58dde3
--- /dev/null
+++ b/lib/edoc/src/edoc_report.erl
@@ -0,0 +1,96 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @private
+%% @copyright 2001-2003 Richard Carlsson
+%% @author Richard Carlsson <[email protected]>
+%% @see edoc
+%% @end
+%% =====================================================================
+
+%% @doc EDoc verbosity/error reporting.
+
+-module(edoc_report).
+
+-export([error/1,
+ error/2,
+ error/3,
+ report/2,
+ report/3,
+ report/4,
+ warning/1,
+ warning/2,
+ warning/3,
+ warning/4]).
+
+-include("edoc.hrl").
+
+
+error(What) ->
+ error([], What).
+
+error(Where, What) ->
+ error(0, Where, What).
+
+error(Line, Where, S) when is_list(S) ->
+ report(Line, Where, S, []);
+error(Line, Where, {S, D}) when is_list(S) ->
+ report(Line, Where, S, D);
+error(Line, Where, {format_error, M, D}) ->
+ report(Line, Where, M:format_error(D), []).
+
+warning(S) ->
+ warning(S, []).
+
+warning(S, Vs) ->
+ warning([], S, Vs).
+
+warning(Where, S, Vs) ->
+ warning(0, Where, S, Vs).
+
+warning(L, Where, S, Vs) ->
+ report(L, Where, "warning: " ++ S, Vs).
+
+report(S, Vs) ->
+ report([], S, Vs).
+
+report(Where, S, Vs) ->
+ report(0, Where, S, Vs).
+
+report(L, Where, S, Vs) ->
+ io:put_chars(where(Where)),
+ if is_integer(L), L > 0 ->
+ io:fwrite("at line ~w: ", [L]);
+ true ->
+ ok
+ end,
+ io:fwrite(S, Vs),
+ io:nl().
+
+where({File, module}) ->
+ io_lib:fwrite("~s, in module header: ", [File]);
+where({File, footer}) ->
+ io_lib:fwrite("~s, in module footer: ", [File]);
+where({File, header}) ->
+ io_lib:fwrite("~s, in header file: ", [File]);
+where({File, {F, A}}) ->
+ io_lib:fwrite("~s, function ~s/~w: ", [File, F, A]);
+where([]) ->
+ io_lib:fwrite("~s: ", [?APPLICATION]);
+where(File) when is_list(File) ->
+ File ++ ": ".
diff --git a/lib/edoc/src/edoc_run.erl b/lib/edoc/src/edoc_run.erl
new file mode 100644
index 0000000000..37025d6621
--- /dev/null
+++ b/lib/edoc/src/edoc_run.erl
@@ -0,0 +1,225 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @copyright 2003 Richard Carlsson
+%% @author Richard Carlsson <[email protected]>
+%% @see edoc
+%% @end
+%% =====================================================================
+
+%% @doc Interface for calling EDoc from Erlang startup options.
+%%
+%% The following is an example of typical usage in a Makefile:
+%% ```docs:
+%% erl -noshell -run edoc_run application "'$(APP_NAME)'" \
+%% '"."' '[{def,{vsn,"$(VSN)"}}]'
+%% '''
+%% (note the single-quotes to avoid shell expansion, and the
+%% double-quotes enclosing the strings).
+%%
+%% <strong>New feature in version 0.6.9</strong>: It is no longer
+%% necessary to write `-s init stop' last on the command line in order
+%% to make the execution terminate. The termination (signalling success
+%% or failure to the operating system) is now built into these
+%% functions.
+
+-module(edoc_run).
+
+-export([file/1, application/1, packages/1, files/1, toc/1]).
+
+-import(edoc_report, [report/2, error/1]).
+
+
+%% @spec application([string()]) -> none()
+%%
+%% @doc Calls {@link edoc:application/3} with the corresponding
+%% arguments. The strings in the list are parsed as Erlang constant
+%% terms. The list can be either `[App]', `[App, Options]' or `[App,
+%% Dir, Options]'. In the first case {@link edoc:application/1} is
+%% called instead; in the second case, {@link edoc:application/2} is
+%% called.
+%%
+%% The function call never returns; instead, the emulator is
+%% automatically terminated when the call has completed, signalling
+%% success or failure to the operating system.
+
+application(Args) ->
+ F = fun () ->
+ case parse_args(Args) of
+ [App] -> edoc:application(App);
+ [App, Opts] -> edoc:application(App, Opts);
+ [App, Dir, Opts] -> edoc:application(App, Dir, Opts);
+ _ ->
+ invalid_args("edoc_run:application/1", Args)
+ end
+ end,
+ run(F).
+
+%% @spec files([string()]) -> none()
+%%
+%% @doc Calls {@link edoc:files/2} with the corresponding arguments. The
+%% strings in the list are parsed as Erlang constant terms. The list can
+%% be either `[Files]' or `[Files, Options]'. In the first case, {@link
+%% edoc:files/1} is called instead.
+%%
+%% The function call never returns; instead, the emulator is
+%% automatically terminated when the call has completed, signalling
+%% success or failure to the operating system.
+
+files(Args) ->
+ F = fun () ->
+ case parse_args(Args) of
+ [Files] -> edoc:files(Files);
+ [Files, Opts] -> edoc:files(Files, Opts);
+ _ ->
+ invalid_args("edoc_run:files/1", Args)
+ end
+ end,
+ run(F).
+
+%% @spec packages([string()]) -> none()
+%%
+%% @doc Calls {@link edoc:application/2} with the corresponding
+%% arguments. The strings in the list are parsed as Erlang constant
+%% terms. The list can be either `[Packages]' or `[Packages, Options]'.
+%% In the first case {@link edoc:application/1} is called instead.
+%%
+%% The function call never returns; instead, the emulator is
+%% automatically terminated when the call has completed, signalling
+%% success or failure to the operating system.
+
+packages(Args) ->
+ F = fun () ->
+ case parse_args(Args) of
+ [Packages] -> edoc:packages(Packages);
+ [Packages, Opts] -> edoc:packages(Packages, Opts);
+ _ ->
+ invalid_args("edoc_run:packages/1", Args)
+ end
+ end,
+ run(F).
+
+%% @hidden Not official yet
+toc(Args) ->
+ F = fun () ->
+ case parse_args(Args) of
+ [Dir, Paths] -> edoc:toc(Dir,Paths);
+ [Dir, Paths, Opts] -> edoc:toc(Dir,Paths,Opts);
+ _ ->
+ invalid_args("edoc_run:toc/1", Args)
+ end
+ end,
+ run(F).
+
+
+%% @spec file([string()]) -> none()
+%%
+%% @deprecated This is part of the old interface to EDoc and is mainly
+%% kept for backwards compatibility. The preferred way of generating
+%% documentation is through one of the functions {@link application/1},
+%% {@link packages/1} and {@link files/1}.
+%%
+%% @doc Calls {@link edoc:file/2} with the corresponding arguments. The
+%% strings in the list are parsed as Erlang constant terms. The list can
+%% be either `[File]' or `[File, Options]'. In the first case, an empty
+%% list of options is passed to {@link edoc:file/2}.
+%%
+%% The following is an example of typical usage in a Makefile:
+%% ```$(DOCDIR)/%.html:%.erl
+%% erl -noshell -run edoc_run file '"$<"' '[{dir,"$(DOCDIR)"}]' \
+%% -s init stop'''
+%%
+%% The function call never returns; instead, the emulator is
+%% automatically terminated when the call has completed, signalling
+%% success or failure to the operating system.
+
+file(Args) ->
+ F = fun () ->
+ case parse_args(Args) of
+ [File] -> edoc:file(File, []);
+ [File, Opts] -> edoc:file(File, Opts);
+ _ ->
+ invalid_args("edoc_run:file/1", Args)
+ end
+ end,
+ run(F).
+
+-spec invalid_args(string(), list()) -> no_return().
+
+invalid_args(Where, Args) ->
+ report("invalid arguments to ~s: ~w.", [Where, Args]),
+ shutdown_error().
+
+run(F) ->
+ wait_init(),
+ case catch {ok, F()} of
+ {ok, _} ->
+ shutdown_ok();
+ {'EXIT', E} ->
+ report("edoc terminated abnormally: ~P.", [E, 10]),
+ shutdown_error();
+ Thrown ->
+ report("internal error: throw without catch in edoc: ~P.",
+ [Thrown, 15]),
+ shutdown_error()
+ end.
+
+wait_init() ->
+ case erlang:whereis(code_server) of
+ undefined ->
+ erlang:yield(),
+ wait_init();
+ _ ->
+ ok
+ end.
+
+%% When and if a function init:stop/1 becomes generally available, we
+%% can use that instead of delay-and-pray when there is an error.
+
+shutdown_ok() ->
+ %% shut down emulator nicely, signalling "normal termination"
+ init:stop().
+
+shutdown_error() ->
+ %% delay 1 second to allow I/O to finish
+ receive after 1000 -> ok end,
+ %% stop emulator the hard way with a nonzero exit value
+ halt(1).
+
+parse_args([A | As]) when is_atom(A) ->
+ [parse_arg(atom_to_list(A)) | parse_args(As)];
+parse_args([A | As]) ->
+ [parse_arg(A) | parse_args(As)];
+parse_args([]) ->
+ [].
+
+parse_arg(A) ->
+ case catch {ok, edoc_lib:parse_expr(A, 1)} of
+ {ok, Expr} ->
+ case catch erl_parse:normalise(Expr) of
+ {'EXIT', _} ->
+ report("bad argument: '~s':", [A]),
+ exit(error);
+ Term ->
+ Term
+ end;
+ {error, _, D} ->
+ report("error parsing argument '~s'", [A]),
+ error(D),
+ exit(error)
+ end.
diff --git a/lib/edoc/src/edoc_scanner.erl b/lib/edoc/src/edoc_scanner.erl
new file mode 100644
index 0000000000..d3dff64682
--- /dev/null
+++ b/lib/edoc/src/edoc_scanner.erl
@@ -0,0 +1,358 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings
+%% AB. Portions created by Ericsson are Copyright 1999, Ericsson
+%% Utvecklings AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+%% @private
+%% @copyright Richard Carlsson 2001-2003. Portions created by Ericsson
+%% are Copyright 1999, Ericsson Utvecklings AB. All Rights Reserved.
+%% @author Richard Carlsson <[email protected]>
+%% @see edoc
+%% @end
+
+%% @doc Tokeniser for EDoc. Based on the Erlang standard library module
+%% {@link //stdlib/erl_scan}.
+
+-module(edoc_scanner).
+
+%% NOTE: the interface to this module is ancient and should be updated.
+%% Please do not regard these exported functions as stable. Their
+%% behaviour is described in the documentation of the module `erl_scan'.
+%%
+%% Since there are no `full stop' tokens in EDoc specifications, the
+%% `tokens' function *always* returns `{more, Continuation}' unless an
+%% error occurs.
+
+-export([string/1,string/2,format_error/1]).
+
+-import(lists, [reverse/1]).
+
+string(Cs) -> string(Cs, 1).
+
+string(Cs, StartPos) ->
+ case scan(Cs, StartPos) of
+ {ok,Toks} -> {ok,Toks,StartPos};
+ {error,E} -> {error,E,StartPos}
+ end.
+
+%% format_error(Error)
+%% Return a string describing the error.
+
+format_error({string,Quote,Head}) ->
+ ["unterminated string starting with " ++ io_lib:write_string(Head,Quote)];
+format_error({illegal,Type}) -> io_lib:fwrite("illegal ~w", [Type]);
+format_error(char) -> "unterminated character";
+format_error(scan) -> "premature end";
+format_error({base,Base}) -> io_lib:fwrite("illegal base '~w'", [Base]);
+format_error(float) -> "bad float";
+
+format_error(Other) -> io_lib:write(Other).
+
+%% Reserved words, not atoms:
+reserved('where') -> true;
+reserved(_) -> false.
+
+%% scan(CharList, StartPos)
+%% This takes a list of characters and tries to tokenise them.
+%%
+%% The token list is built in reverse order (in a stack) to save appending
+%% and then reversed when all the tokens have been collected. Most tokens
+%% are built in the same way.
+%%
+%% Returns:
+%% {ok,[Tok]}
+%% {error,{ErrorPos,edoc_scanner,What}}
+
+scan(Cs, Pos) ->
+ scan1(Cs, [], Pos).
+
+%% scan1(Characters, TokenStack, Position)
+%% Scan a list of characters into tokens.
+
+scan1([$\n|Cs], Toks, Pos) -> % Newline
+ scan1(Cs, Toks, Pos+1);
+scan1([C|Cs], Toks, Pos) when C >= 0, C =< $ -> % Skip blanks
+ scan1(Cs, Toks, Pos);
+scan1([C|Cs], Toks, Pos) when C >= $a, C =< $z -> % Unquoted atom
+ scan_atom(C, Cs, Toks, Pos);
+scan1([C|Cs], Toks, Pos) when C >= $0, C =< $9 -> % Numbers
+ scan_number(C, Cs, Toks, Pos);
+scan1([$-,C| Cs], Toks, Pos) when C >= $0, C =< $9 -> % Signed numbers
+ scan_signed_number($-, C, Cs, Toks, Pos);
+scan1([$+,C| Cs], Toks, Pos) when C >= $0, C =< $9 -> % Signed numbers
+ scan_signed_number($+, C, Cs, Toks, Pos);
+scan1([C|Cs], Toks, Pos) when C >= $A, C =< $Z -> % Variables
+ scan_variable(C, Cs, Toks, Pos);
+scan1([$_|Cs], Toks, Pos) -> % Variables
+ scan_variable($_, Cs, Toks, Pos);
+scan1([$$|Cs], Toks, Pos) -> % Character constant
+ case scan_char_const(Cs, Toks, Pos) of
+ {ok, Result} ->
+ {ok, Result};
+ {error, truncated_char} ->
+ scan_error(char, Pos);
+ {error, illegal_character} ->
+ scan_error({illegal, char}, Pos)
+ end;
+scan1([$'|Cs0], Toks, Pos) -> % Quoted atom
+ case scan_string(Cs0, $', Pos) of
+ {S,Cs1,Pos1} ->
+ case catch list_to_atom(S) of
+ A when is_atom(A) ->
+ scan1(Cs1, [{atom,Pos,A}|Toks], Pos1);
+ _Error -> scan_error({illegal,atom}, Pos)
+ end;
+ {error, premature_end} ->
+ scan_error({string,$',Cs0}, Pos);
+ {error, truncated_char} ->
+ scan_error(char, Pos);
+ {error, illegal_character} ->
+ scan_error({illegal, atom}, Pos)
+ end;
+scan1([$"|Cs0], Toks, Pos) -> % String
+ case scan_string(Cs0, $", Pos) of
+ {S,Cs1,Pos1} ->
+ case Toks of
+ [{string, Pos0, S0} | Toks1] ->
+ scan1(Cs1, [{string, Pos0, S0 ++ S} | Toks1],
+ Pos1);
+ _ ->
+ scan1(Cs1, [{string,Pos,S}|Toks], Pos1)
+ end;
+ {error, premature_end} ->
+ scan_error({string,$",Cs0}, Pos);
+ {error, truncated_char} ->
+ scan_error(char, Pos);
+ {error, illegal_character} ->
+ scan_error({illegal, string}, Pos)
+ end;
+%% Punctuation characters and operators, first recognise multiples.
+scan1([$-,$>|Cs], Toks, Pos) ->
+ scan1(Cs, [{'->',Pos}|Toks], Pos);
+scan1([$:,$:|Cs], Toks, Pos) ->
+ scan1(Cs, [{'::',Pos}|Toks], Pos);
+scan1([$/,$/|Cs], Toks, Pos) ->
+ scan1(Cs, [{'//',Pos}|Toks], Pos);
+scan1([C|Cs], Toks, Pos) -> % Punctuation character
+ P = list_to_atom([C]),
+ scan1(Cs, [{P,Pos}|Toks], Pos);
+scan1([], Toks0, _Pos) ->
+ Toks = reverse(Toks0),
+ {ok,Toks}.
+
+%% Note that `_' is not accepted as a variable token.
+scan_variable(C, Cs, Toks, Pos) ->
+ {Wcs,Cs1} = scan_name(Cs, []),
+ W = [C|reverse(Wcs)],
+ case W of
+ "_" ->
+ scan_error({illegal,token}, Pos);
+ _ ->
+ case catch list_to_atom(W) of
+ A when is_atom(A) ->
+ scan1(Cs1, [{var,Pos,A}|Toks], Pos);
+ _ ->
+ scan_error({illegal,variable}, Pos)
+ end
+ end.
+
+scan_atom(C, Cs, Toks, Pos) ->
+ {Wcs,Cs1} = scan_name(Cs, []),
+ W = [C|reverse(Wcs)],
+ case catch list_to_atom(W) of
+ A when is_atom(A) ->
+ case reserved(A) of
+ true ->
+ scan1(Cs1, [{A,Pos}|Toks], Pos);
+ false ->
+ scan1(Cs1, [{atom,Pos,A}|Toks], Pos)
+ end;
+ _ ->
+ scan_error({illegal,token}, Pos)
+ end.
+
+%% scan_name(Cs) -> lists:splitwith(fun (C) -> name_char(C) end, Cs).
+
+scan_name([C|Cs], Ncs) ->
+ case name_char(C) of
+ true ->
+ scan_name(Cs, [C|Ncs]);
+ false ->
+ {Ncs,[C|Cs]} % Must rebuild here, sigh!
+ end;
+scan_name([], Ncs) ->
+ {Ncs,[]}.
+
+name_char(C) when C >= $a, C =< $z -> true;
+name_char(C) when C >= $\337, C =< $\377, C /= $\367 -> true;
+name_char(C) when C >= $A, C =< $Z -> true;
+name_char(C) when C >= $\300, C =< $\336, C /= $\327 -> true;
+name_char(C) when C >= $0, C =< $9 -> true;
+name_char($_) -> true;
+name_char($@) -> true;
+name_char(_) -> false.
+
+%% scan_string(CharList, QuoteChar, Pos) ->
+%% {StringChars,RestChars, NewPos}
+
+scan_string(Cs, Quote, Pos) ->
+ scan_string(Cs, [], Quote, Pos).
+
+scan_string([Quote|Cs], Scs, Quote, Pos) ->
+ {reverse(Scs),Cs,Pos};
+scan_string([], _Scs, _Quote, _Pos) ->
+ {error, premature_end};
+scan_string(Cs0, Scs, Quote, Pos) ->
+ case scan_char(Cs0, Pos) of
+ {C,Cs,Pos1} ->
+ %% Only build the string here
+ scan_string(Cs, [C|Scs], Quote, Pos1);
+ Error ->
+ Error
+ end.
+
+%% Note that space characters are not allowed
+scan_char_const([$\040 | _Cs0], _Toks, _Pos) ->
+ {error, illegal_character};
+scan_char_const(Cs0, Toks, Pos) ->
+ case scan_char(Cs0, Pos) of
+ {C,Cs,Pos1} ->
+ scan1(Cs, [{char,Pos,C}|Toks], Pos1);
+ Error ->
+ Error
+ end.
+
+%% {Character,RestChars,NewPos} = scan_char(Chars, Pos)
+%% Read a single character from a string or character constant. The
+%% pre-scan phase has checked for errors here.
+%% Note that control characters are not allowed.
+
+scan_char([$\\|Cs], Pos) ->
+ scan_escape(Cs, Pos);
+scan_char([C | _Cs], _Pos) when C =< 16#1f ->
+ {error, illegal_character};
+scan_char([C|Cs], Pos) ->
+ {C,Cs,Pos};
+scan_char([], _Pos) ->
+ {error, truncated_char}.
+
+%% The following conforms to Standard Erlang escape sequences.
+
+scan_escape([O1, O2, O3 | Cs], Pos) when % \<1-3> octal digits
+ O1 >= $0, O1 =< $3, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 ->
+ Val = (O1*8 + O2)*8 + O3 - 73*$0,
+ {Val,Cs,Pos};
+scan_escape([O1, O2 | Cs], Pos) when
+ O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7 ->
+ Val = (O1*8 + O2) - 9*$0,
+ {Val,Cs,Pos};
+scan_escape([O1 | Cs], Pos) when
+ O1 >= $0, O1 =< $7 ->
+ {O1 - $0,Cs,Pos};
+scan_escape([$^, C | Cs], Pos) -> % \^X -> CTL-X
+ if C >= $\100, C =< $\137 ->
+ {C - $\100,Cs,Pos};
+ true -> {error, illegal_control_character}
+ end;
+scan_escape([C | Cs], Pos) ->
+ case escape_char(C) of
+ C1 when C1 > $\000 -> {C1,Cs,Pos};
+ _ -> {error, undefined_escape_sequence}
+ end;
+scan_escape([], _Pos) ->
+ {error, truncated_char}.
+
+%% Note that we return $\000 for undefined escapes.
+escape_char($b) -> $\010; % \b = BS
+escape_char($d) -> $\177; % \d = DEL
+escape_char($e) -> $\033; % \e = ESC
+escape_char($f) -> $\014; % \f = FF
+escape_char($n) -> $\012; % \n = LF
+escape_char($r) -> $\015; % \r = CR
+escape_char($s) -> $\040; % \s = SPC
+escape_char($t) -> $\011; % \t = HT
+escape_char($v) -> $\013; % \v = VT
+escape_char($\\) -> $\134; % \\ = \
+escape_char($') -> $\047; % \' = '
+escape_char($") -> $\042; % \" = "
+escape_char(_C) -> $\000.
+
+%% scan_number(Char, CharList, TokenStack, Pos)
+%% We handle sign and radix notation:
+%% [+-]<digits> - the digits in base [+-]10
+%% [+-]<digits>.<digits>
+%% [+-]<digits>.<digits>E+-<digits>
+%% [+-]<digits>#<digits> - the digits read in base [+-]B
+%%
+%% Except for explicitly based integers we build a list of all the
+%% characters and then use list_to_integer/1 or list_to_float/1 to
+%% generate the value.
+
+%% SPos == Start position
+%% CPos == Current position
+
+scan_number(C, Cs0, Toks, Pos) ->
+ {Ncs,Cs,Pos1} = scan_integer(Cs0, [C], Pos),
+ scan_after_int(Cs, Ncs, Toks, Pos, Pos1).
+
+scan_signed_number(S, C, Cs0, Toks, Pos) ->
+ {Ncs,Cs,Pos1} = scan_integer(Cs0, [C, S], Pos),
+ scan_after_int(Cs, Ncs, Toks, Pos, Pos1).
+
+scan_integer([C|Cs], Stack, Pos) when C >= $0, C =< $9 ->
+ scan_integer(Cs, [C|Stack], Pos);
+scan_integer(Cs, Stack, Pos) ->
+ {Stack,Cs,Pos}.
+
+scan_after_int([$.,C|Cs0], Ncs0, Toks, SPos, CPos) when C >= $0, C =< $9 ->
+ {Ncs,Cs,CPos1} = scan_integer(Cs0, [C,$.|Ncs0], CPos),
+ scan_after_fraction(Cs, Ncs, Toks, SPos, CPos1);
+scan_after_int(Cs, Ncs, Toks, SPos, CPos) ->
+ N = list_to_integer(reverse(Ncs)),
+ scan1(Cs, [{integer,SPos,N}|Toks], CPos).
+
+scan_after_fraction([$E|Cs], Ncs, Toks, SPos, CPos) ->
+ scan_exponent(Cs, [$E|Ncs], Toks, SPos, CPos);
+scan_after_fraction([$e|Cs], Ncs, Toks, SPos, CPos) ->
+ scan_exponent(Cs, [$e|Ncs], Toks, SPos, CPos);
+scan_after_fraction(Cs, Ncs, Toks, SPos, CPos) ->
+ case catch list_to_float(reverse(Ncs)) of
+ N when is_float(N) ->
+ scan1(Cs, [{float,SPos,N}|Toks], CPos);
+ _Error -> scan_error({illegal,float}, SPos)
+ end.
+
+%% scan_exponent(CharList, NumberCharStack, TokenStack, StartPos, CurPos)
+%% Generate an error here if E{+|-} not followed by any digits.
+
+scan_exponent([$+|Cs], Ncs, Toks, SPos, CPos) ->
+ scan_exponent1(Cs, [$+|Ncs], Toks, SPos, CPos);
+scan_exponent([$-|Cs], Ncs, Toks, SPos, CPos) ->
+ scan_exponent1(Cs, [$-|Ncs], Toks, SPos, CPos);
+scan_exponent(Cs, Ncs, Toks, SPos, CPos) ->
+ scan_exponent1(Cs, Ncs, Toks, SPos, CPos).
+
+scan_exponent1([C|Cs0], Ncs0, Toks, SPos, CPos) when C >= $0, C =< $9 ->
+ {Ncs,Cs,CPos1} = scan_integer(Cs0, [C|Ncs0], CPos),
+ case catch list_to_float(reverse(Ncs)) of
+ N when is_float(N) ->
+ scan1(Cs, [{float,SPos,N}|Toks], CPos1);
+ _Error -> scan_error({illegal,float}, SPos)
+ end;
+scan_exponent1(_, _, _, _, CPos) ->
+ scan_error(float, CPos).
+
+scan_error(In, Pos) ->
+ {error,{Pos,edoc_scanner,In}}.
diff --git a/lib/edoc/src/edoc_tags.erl b/lib/edoc/src/edoc_tags.erl
new file mode 100644
index 0000000000..1f2cb99c75
--- /dev/null
+++ b/lib/edoc/src/edoc_tags.erl
@@ -0,0 +1,373 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @private
+%% @copyright 2001-2003 Richard Carlsson
+%% @author Richard Carlsson <[email protected]>
+%% @see edoc
+%% @end
+%% =====================================================================
+
+%% @doc EDoc tag scanning.
+
+%% TODO: tag/macro for including the code of a function as `<pre>'-text.
+%% TODO: consider new tag: @license text
+
+-module(edoc_tags).
+
+-export([tags/0, tags/1, tag_names/0, tag_parsers/0, scan_lines/2,
+ filter_tags/3, check_tags/4, parse_tags/4]).
+
+-import(edoc_report, [report/4, warning/4, error/3]).
+
+-include("edoc.hrl").
+-include("edoc_types.hrl").
+
+
+%% Tags are described by {Name, Parser, Flags}.
+%% Name = atom()
+%% Parser = text | xml | (Text,Line,Where) -> term()
+%% Flags = [Flag]
+%% Flag = module | function | package | overview | single
+%%
+%% Note that the pseudo-tag '@clear' is not listed here.
+%% (Cf. the function 'filter_tags'.)
+%%
+%% Rejected tag suggestions:
+%% - @keywords (never up to date; free text search is better)
+%% - @uses [modules] (never up to date; false dependencies)
+%% - @maintainer (never up to date; duplicates author info)
+%% - @contributor (unnecessary; mention in normal documentation)
+%% - @creator (unnecessary; already have copyright/author)
+%% - @history (never properly updated; use version control etc.)
+%% - @category (useless; superseded by keywords or free text search)
+
+tags() ->
+ All = [module,footer,function,package,overview],
+ [{author, fun parse_contact/4, [module,package,overview]},
+ {copyright, text, [module,package,overview,single]},
+ {deprecated, xml, [module,function,package,single]},
+ {doc, xml, [module,function,package,overview,single]},
+ {docfile, fun parse_file/4, All},
+ {'end', text, All},
+ {equiv, fun parse_expr/4, [function,single]},
+ {headerfile, fun parse_header/4, All},
+ {hidden, text, [module,function,single]},
+ {param, fun parse_param/4, [function]},
+ {private, text, [module,function,single]},
+ {reference, xml, [module,footer,package,overview]},
+ {returns, xml, [function,single]},
+ {see, fun parse_see/4, [module,function,package,overview]},
+ {since, text, [module,function,package,overview,single]},
+ {spec, fun parse_spec/4, [function,single]},
+ {throws, fun parse_throws/4, [function,single]},
+ {title, text, [overview,single]},
+ {'TODO', xml, All},
+ {todo, xml, All},
+ {type, fun parse_typedef/4, [module,footer,function]},
+ {version, text, [module,package,overview,single]}].
+
+aliases('TODO') -> todo;
+aliases(return) -> returns;
+aliases(T) -> T.
+
+%% Selecting tags based on flags.
+tags(Flag) ->
+ [T || {T,_,Fs} <- tags(), lists:member(Flag, Fs)].
+
+%% The set of known tags.
+tag_names() ->
+ [T || {T,_,_} <- tags()].
+
+%% The pairs of tags and their parsers.
+tag_parsers() ->
+ [{T,F} || {T,F,_} <- tags()].
+
+
+%% Scanning lines of comment text.
+
+scan_lines(Ss, L) ->
+ lists:reverse(scan_lines(Ss, L, [])).
+
+scan_lines([S | Ss], L, As) ->
+ scan_lines(S, Ss, L, As);
+scan_lines([], _L, As) ->
+ As.
+
+%% Looking for a leading '@', skipping whitespace.
+%% Also accept "TODO:" at start of line as equivalent to "@TODO".
+
+scan_lines([$\s | Cs], Ss, L, As) -> scan_lines(Cs, Ss, L, As);
+scan_lines([$\t | Cs], Ss, L, As) -> scan_lines(Cs, Ss, L, As);
+scan_lines([$@ | Cs], Ss, L, As) -> scan_tag(Cs, Ss, L, As, []);
+scan_lines(("TODO:"++_)=Cs, Ss, L, As) -> scan_tag(Cs, Ss, L, As, []);
+scan_lines(_, Ss, L, As) -> scan_lines(Ss, L + 1, As).
+
+%% Scanning chars following '@', accepting only nonempty valid names.
+%% See edoc_lib:is_name/1 for details on what is a valid name. In tags
+%% we also allow the initial letter to be uppercase or underscore.
+
+scan_tag([C | Cs], Ss, L, As, Ts) when C >= $a, C =< $z ->
+ scan_tag_1(Cs, Ss, L, As, [C | Ts]);
+scan_tag([C | Cs], Ss, L, As, Ts) when C >= $A, C =< $Z ->
+ scan_tag_1(Cs, Ss, L, As, [C | Ts]);
+scan_tag([C | Cs], Ss, L, As, Ts) when C >= $\300, C =< $\377,
+ C =/= $\327, C =/= $\367 ->
+ scan_tag_1(Cs, Ss, L, As, [C | Ts]);
+scan_tag([$_ | Cs], Ss, L, As, Ts) ->
+ scan_tag_1(Cs, Ss, L, As, [$_ | Ts]);
+scan_tag(_Cs, Ss, L, As, _Ts) ->
+ scan_lines(Ss, L + 1, As). % not a valid name
+
+scan_tag_1([C | Cs], Ss, L, As, Ts) when C >= $a, C =< $z ->
+ scan_tag_1(Cs, Ss, L, As, [C | Ts]);
+scan_tag_1([C | Cs], Ss, L, As, Ts) when C >= $A, C =< $Z ->
+ scan_tag_1(Cs, Ss, L, As, [C | Ts]);
+scan_tag_1([C | Cs], Ss, L, As, Ts) when C >= $0, C =< $9 ->
+ scan_tag_1(Cs, Ss, L, As, [C | Ts]);
+scan_tag_1([C | Cs], Ss, L, As, Ts) when C >= $\300, C =< $\377,
+ C =/= $\327, C =/= $\367 ->
+ scan_tag_1(Cs, Ss, L, As, [C | Ts]);
+scan_tag_1([$_ | Cs], Ss, L, As, Ts) ->
+ scan_tag_1(Cs, Ss, L, As, [$_ | Ts]);
+scan_tag_1(Cs, Ss, L, As, Ts) ->
+ scan_tag_2(Cs, Ss, L, As, {Ts, L}).
+
+%% Check that the tag is followed by whitespace, linebreak, or colon.
+
+scan_tag_2([$\s | Cs], Ss, L, As, T) ->
+ scan_tag_lines(Ss, T, [Cs], L + 1, As);
+scan_tag_2([$\t | Cs], Ss, L, As, T) ->
+ scan_tag_lines(Ss, T, [Cs], L + 1, As);
+scan_tag_2([$: | Cs], Ss, L, As, T) ->
+ scan_tag_lines(Ss, T, [Cs], L + 1, As);
+scan_tag_2([], Ss, L, As, T) ->
+ scan_tag_lines(Ss, T, [[]], L + 1, As);
+scan_tag_2(_, Ss, L, As, _T) ->
+ scan_lines(Ss, L + 1, As).
+
+%% Scanning lines after a tag is found.
+
+scan_tag_lines([S | Ss], T, Ss1, L, As) ->
+ scan_tag_lines(S, S, Ss, T, Ss1, L, As);
+scan_tag_lines([], {Ts, L1}, Ss1, _L, As) ->
+ [make_tag(Ts, L1, Ss1) | As].
+
+%% Collecting tag text lines until end of comment or next tagged line.
+
+scan_tag_lines([$\s | Cs], S, Ss, T, Ss1, L, As) ->
+ scan_tag_lines(Cs, S, Ss, T, Ss1, L, As);
+scan_tag_lines([$\t | Cs], S, Ss, T, Ss1, L, As) ->
+ scan_tag_lines(Cs, S, Ss, T, Ss1, L, As);
+scan_tag_lines([$@, C | _Cs], S, Ss, {Ts, L1}, Ss1, L, As)
+ when C >= $a, C =< $z ->
+ scan_lines(S, Ss, L, [make_tag(Ts, L1, Ss1) | As]);
+scan_tag_lines([$@, C | _Cs], S, Ss, {Ts, L1}, Ss1, L, As)
+ when C >= $A, C =< $Z ->
+ scan_lines(S, Ss, L, [make_tag(Ts, L1, Ss1) | As]);
+scan_tag_lines([$@, C | _Cs], S, Ss, {Ts, L1}, Ss1, L, As)
+ when C >= $\300, C =< $\377, C =/= $\327, C =/= $\367 ->
+ scan_lines(S, Ss, L, [make_tag(Ts, L1, Ss1) | As]);
+scan_tag_lines("TODO:"++_, S, Ss, {Ts, L1}, Ss1, L, As) ->
+ scan_lines(S, Ss, L, [make_tag(Ts, L1, Ss1) | As]);
+scan_tag_lines(_Cs, S, Ss, T, Ss1, L, As) ->
+ scan_tag_lines(Ss, T, [S | Ss1], L + 1, As).
+
+make_tag(Cs, L, Ss) ->
+ #tag{name = aliases(list_to_atom(lists:reverse(Cs))),
+ line = L,
+ data = append_lines(lists:reverse(Ss))}.
+
+%% Flattening lines of text and inserting line breaks.
+
+append_lines([L]) -> L;
+append_lines([L | Ls]) -> L ++ [$\n | append_lines(Ls)];
+append_lines([]) -> [].
+
+%% Filtering out unknown tags.
+
+filter_tags(Ts, Tags, Where) ->
+ filter_tags(Ts, Tags, Where, []).
+
+filter_tags([#tag{name = clear} | Ts], Tags, Where, _Ts1) ->
+ filter_tags(Ts, Tags, Where);
+filter_tags([#tag{name = N, line = L} = T | Ts], Tags, Where, Ts1) ->
+ case sets:is_element(N, Tags) of
+ true ->
+ filter_tags(Ts, Tags, Where, [T | Ts1]);
+ false ->
+ warning(L, Where, "tag @~s not recognized.", [N]),
+ filter_tags(Ts, Tags, Where, Ts1)
+ end;
+filter_tags([], _, _, Ts) ->
+ lists:reverse(Ts).
+
+%% Check occurrances of tags.
+
+check_tags(Ts, Allow, Single, Where) ->
+ check_tags(Ts, Allow, Single, Where, false, sets:new()).
+
+check_tags([#tag{name = T, line = L} | Ts], Allow, Single, Where, Error, Seen) ->
+ case sets:is_element(T, Seen) of
+ true ->
+ case sets:is_element(T, Single) of
+ false ->
+ check_tags(Ts, Allow, Single, Where, Error, Seen);
+ true ->
+ report(L, Where, "multiple @~s tag.", [T]),
+ check_tags(Ts, Allow, Single, Where, true, Seen)
+ end;
+ false ->
+ Seen1 = sets:add_element(T, Seen),
+ case sets:is_element(T, Allow) of
+ true ->
+ check_tags(Ts, Allow, Single, Where, Error, Seen1);
+ false ->
+ report(L, Where, "tag @~s not allowed here.", [T]),
+ check_tags(Ts, Allow, Single, Where, true, Seen1)
+ end
+ end;
+check_tags([], _, _, _, Error, _) ->
+ Error.
+
+
+%% Parses tag contents for specific tags.
+
+parse_tags(Ts, How, Env, Where) ->
+ parse_tags(Ts, How, Env, Where, []).
+
+parse_tags([#tag{name = Name} = T | Ts], How, Env, Where, Ts1) ->
+ case dict:fetch(Name, How) of
+ text ->
+ parse_tags(Ts, How, Env, Where, [T | Ts1]);
+ xml ->
+ [T1] = parse_tag(T, fun parse_xml/4, Env, Where),
+ parse_tags(Ts, How, Env, Where, [T1 | Ts1]);
+ F when is_function(F) ->
+ Ts2 = parse_tag(T, F, Env, Where),
+ parse_tags(Ts, How, Env, Where, lists:reverse(Ts2, Ts1))
+ end;
+parse_tags([], _How, _Env, _Where, Ts) ->
+ lists:reverse(Ts).
+
+parse_tag(T, F, Env, Where) ->
+ case catch {ok, F(T#tag.data, T#tag.line, Env, Where)} of
+ {ok, Data} ->
+ [T#tag{data = Data}];
+ {expand, Ts} ->
+ Ts;
+ {error, L, Error} ->
+ error(L, Where, Error),
+ exit(error);
+ {'EXIT', R} -> exit(R);
+ Other -> throw(Other)
+ end.
+
+%% parser functions for the built-in content types. They also perform
+%% some sanity checks on the results.
+
+parse_xml(Data, Line, _Env, _Where) ->
+ edoc_wiki:parse_xml(Data, Line).
+
+parse_see(Data, Line, _Env, _Where) ->
+ edoc_parser:parse_see(Data, Line).
+
+parse_expr(Data, Line, _Env, _Where) ->
+ edoc_lib:parse_expr(Data, Line).
+
+parse_spec(Data, Line, _Env, {_, {F, A}} = _Where) ->
+ Spec = edoc_parser:parse_spec(Data, Line),
+ #t_spec{name = N, type = #t_fun{args = As}} = Spec,
+ if length(As) /= A ->
+ throw_error(Line, "@spec arity does not match.");
+ true ->
+ case N of
+ undefined ->
+ Spec#t_spec{name = #t_name{module = [], name = F}};
+ #t_name{module = [], name = F} ->
+ Spec;
+ _ ->
+ throw_error(Line, "@spec name does not match.")
+ end
+ end.
+
+parse_param(Data, Line, _Env, {_, {_F, _A}} = _Where) ->
+ edoc_parser:parse_param(Data, Line).
+
+parse_throws(Data, Line, _Env, {_, {_F, _A}} = _Where) ->
+ edoc_parser:parse_throws(Data, Line).
+
+parse_contact(Data, Line, _Env, _Where) ->
+ case edoc_lib:parse_contact(Data, Line) of
+ {"", "", _URI} ->
+ throw_error(Line, "must specify name or e-mail.");
+ Info ->
+ Info
+ end.
+
+parse_typedef(Data, Line, _Env, _Where) ->
+ Def = edoc_parser:parse_typedef(Data, Line),
+ {#t_typedef{name = #t_name{name = T}}, _} = Def,
+ case edoc_types:is_predefined(T) of
+ true ->
+ throw_error(Line, {"redefining built-in type '~w'.", [T]});
+ false ->
+ Def
+ end.
+
+parse_file(Data, Line, Env, _Where) ->
+ case edoc_lib:parse_expr(Data, Line) of
+ {string, _, File0} ->
+ File = edoc_lib:strip_space(File0),
+ case edoc_extract:file(File, module, Env, []) of
+ {ok, Ts} ->
+ throw({expand, Ts});
+ {error, R} ->
+ throw_error(Line, {read_file, File, R})
+ end;
+ _ ->
+ throw_error(Line, file_not_string)
+ end.
+
+parse_header(Data, Line, Env, {Where, _}) ->
+ parse_header(Data, Line, Env, Where);
+parse_header(Data, Line, Env, Where) when is_list(Where) ->
+ case edoc_lib:parse_expr(Data, Line) of
+ {string, _, File} ->
+ Dir = filename:dirname(Where),
+ Path = Env#env.includes ++ [Dir],
+ case edoc_lib:find_file(Path, "", File) of
+ "" ->
+ throw_error(Line, {file_not_found, File});
+ File1 ->
+ Ts = edoc_extract:header(File1, Env, []),
+ throw({expand, Ts})
+ end;
+ _ ->
+ throw_error(Line, file_not_string)
+ end.
+
+throw_error(L, {read_file, File, R}) ->
+ throw_error(L, {"error reading file '~s': ~w",
+ [edoc_lib:filename(File), R]});
+throw_error(L, {file_not_found, F}) ->
+ throw_error(L, {"file not found: ~s", [F]});
+throw_error(L, file_not_string) ->
+ throw_error(L, "expected file name as a string");
+throw_error(L, D) ->
+ throw({error, L, D}).
diff --git a/lib/edoc/src/edoc_types.erl b/lib/edoc/src/edoc_types.erl
new file mode 100644
index 0000000000..85c9ee6f2a
--- /dev/null
+++ b/lib/edoc/src/edoc_types.erl
@@ -0,0 +1,204 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @private
+%% @copyright 2001-2003 Richard Carlsson
+%% @author Richard Carlsson <[email protected]>
+%% @see edoc
+%% @end
+%% =====================================================================
+
+%% @doc Datatype representation for EDoc.
+
+-module(edoc_types).
+
+-export([is_predefined/1, to_ref/1, to_xml/2, to_label/1, arg_names/1,
+ set_arg_names/2, arg_descs/1, range_desc/1]).
+
+%% @headerfile "edoc_types.hrl"
+
+-include("edoc_types.hrl").
+-include("xmerl.hrl").
+
+
+is_predefined(any) -> true;
+is_predefined(atom) -> true;
+is_predefined(binary) -> true;
+is_predefined(bool) -> true;
+is_predefined(char) -> true;
+is_predefined(cons) -> true;
+is_predefined(deep_string) -> true;
+is_predefined(float) -> true;
+is_predefined(function) -> true;
+is_predefined(integer) -> true;
+is_predefined(list) -> true;
+is_predefined(nil) -> true;
+is_predefined(none) -> true;
+is_predefined(number) -> true;
+is_predefined(pid) -> true;
+is_predefined(port) -> true;
+is_predefined(reference) -> true;
+is_predefined(string) -> true;
+is_predefined(term) -> true;
+is_predefined(tuple) -> true;
+is_predefined(_) -> false.
+
+to_ref(#t_typedef{name = N}) ->
+ to_ref(N);
+to_ref(#t_def{name = N}) ->
+ to_ref(N);
+to_ref(#t_type{name = N}) ->
+ to_ref(N);
+to_ref(#t_name{module = [], name = N}) ->
+ edoc_refs:type(N);
+to_ref(#t_name{app = [], module = M, name = N}) ->
+ edoc_refs:type(M, N);
+to_ref(#t_name{app = A, module = M, name = N}) ->
+ edoc_refs:type(A, M, N).
+
+to_label(N) ->
+ edoc_refs:to_label(to_ref(N)).
+
+get_uri(Name, Env) ->
+ edoc_refs:get_uri(to_ref(Name), Env).
+
+to_xml(#t_var{name = N}, _Env) ->
+ {typevar, [{name, atom_to_list(N)}], []};
+to_xml(#t_name{module = [], name = N}, _Env) ->
+ {erlangName, [{name, atom_to_list(N)}], []};
+to_xml(#t_name{app = [], module = M, name = N}, _Env) ->
+ {erlangName, [{module, atom_to_list(M)},
+ {name, atom_to_list(N)}], []};
+to_xml(#t_name{app = A, module = M, name = N}, _Env) ->
+ {erlangName, [{app, atom_to_list(A)},
+ {module, atom_to_list(M)},
+ {name, atom_to_list(N)}], []};
+to_xml(#t_type{name = N, args = As}, Env) ->
+ Predef = case N of
+ #t_name{module = [], name = T} ->
+ is_predefined(T);
+ _ ->
+ false
+ end,
+ HRef = case Predef of
+ true -> [];
+ false -> [{href, get_uri(N, Env)}]
+ end,
+ {abstype, HRef, [to_xml(N, Env) | map(fun wrap_utype/2, As, Env)]};
+to_xml(#t_fun{args = As, range = T}, Env) ->
+ {'fun', [{argtypes, map(fun wrap_utype/2, As, Env)},
+ wrap_utype(T, Env)]};
+to_xml(#t_tuple{types = Ts}, Env) ->
+ {tuple, map(fun wrap_utype/2, Ts, Env)};
+to_xml(#t_list{type = T}, Env) ->
+ {list, [wrap_utype(T, Env)]};
+to_xml(#t_nil{}, _Env) ->
+ nil;
+to_xml(#t_atom{val = V}, _Env) ->
+ {atom, [{value, io_lib:write(V)}], []};
+to_xml(#t_integer{val = V}, _Env) ->
+ {integer, [{value, integer_to_list(V)}], []};
+to_xml(#t_float{val = V}, _Env) ->
+ {float, [{value, io_lib:write(V)}], []};
+to_xml(#t_union{types = Ts}, Env) ->
+ {union, map(fun wrap_type/2, Ts, Env)};
+to_xml(#t_record{name = N = #t_atom{}, fields = Fs}, Env) ->
+ {record, [to_xml(N, Env) | map(fun to_xml/2, Fs, Env)]};
+to_xml(#t_field{name = N = #t_atom{}, type = T}, Env) ->
+ {field, [to_xml(N, Env), wrap_type(T, Env)]};
+to_xml(#t_def{name = N = #t_var{}, type = T}, Env) ->
+ {localdef, [to_xml(N, Env), wrap_type(T, Env)]};
+to_xml(#t_def{name = N, type = T}, Env) ->
+ {localdef, [{label, to_label(N)}],
+ [to_xml(N, Env), wrap_type(T, Env)]};
+to_xml(#t_spec{name = N, type = T, defs = Ds}, Env) ->
+ {typespec, [to_xml(N, Env), wrap_utype(T, Env)
+ | map(fun to_xml/2, Ds, Env)]};
+to_xml(#t_typedef{name = N, args = As, type = undefined, defs = Ds},
+ Env) ->
+ {typedef, [to_xml(N, Env),
+ {argtypes, map(fun wrap_utype/2, As, Env)}
+ | map(fun to_xml/2, Ds, Env)]};
+to_xml(#t_typedef{name = N, args = As, type = T, defs = Ds}, Env) ->
+ {typedef, [to_xml(N, Env),
+ {argtypes, map(fun wrap_utype/2, As, Env)},
+ wrap_type(T, Env)
+ | map(fun to_xml/2, Ds, Env)]};
+to_xml(#t_throws{type = T, defs = Ds}, Env) ->
+ {throws, [wrap_type(T, Env)
+ | map(fun to_xml/2, Ds, Env)]}.
+
+wrap_type(T, Env) ->
+ {type, [to_xml(T, Env)]}.
+
+wrap_utype(T, Env) ->
+ E = to_xml(T, Env),
+ case arg_name(T) of
+ '_' -> {type, [E]};
+ A -> {type, [{name, atom_to_list(A)}], [E]}
+ end.
+
+map(F, Xs, Env) ->
+ [F(X, Env) || X <- Xs].
+
+is_name(A) when is_atom(A) -> true;
+is_name(_) -> false.
+
+is_desc(A) when is_list(A) -> true;
+is_desc(_) -> false.
+
+arg_name(T) ->
+ find(?t_ann(T), fun is_name/1, '_').
+
+arg_names(S) ->
+ arg_anns(S, fun is_name/1, '_').
+
+arg_descs(S) ->
+ arg_anns(S, fun is_desc/1, "").
+
+range_desc(#t_spec{type = #t_fun{range = T}}) ->
+ find(?t_ann(T), fun is_desc/1, "").
+
+arg_anns(#t_spec{type = #t_fun{args = As}}, F, Def) ->
+ [find(?t_ann(A), F, Def) || A <- As].
+
+find([A| As], F, Def) ->
+ case F(A) of
+ true -> A;
+ false -> find(As, F, Def)
+ end;
+find([], _, Def) -> Def.
+
+set_arg_names(S, Ns) ->
+ set_arg_anns(S, Ns, fun is_name/1).
+
+%% set_arg_descs(S, Ns) ->
+%% set_arg_anns(S, Ns, fun is_desc/1).
+
+set_arg_anns(#t_spec{type = #t_fun{args = As}=T}=S, Ns, F) ->
+ Zip = fun (A, N) ->
+ ?set_t_ann(A, update(?t_ann(A), N, F))
+ end,
+ S#t_spec{type = T#t_fun{args = lists:zipwith(Zip, As, Ns)}}.
+
+update([A| As], N, F) ->
+ case F(A) of
+ true -> [N | As];
+ false -> [A| update(As, N, F)]
+ end;
+update([], N, _) -> [N].
diff --git a/lib/edoc/src/edoc_types.hrl b/lib/edoc/src/edoc_types.hrl
new file mode 100644
index 0000000000..1dcbdd9493
--- /dev/null
+++ b/lib/edoc/src/edoc_types.hrl
@@ -0,0 +1,130 @@
+%% =====================================================================
+%% Header file for EDoc Type Representations
+%%
+%% Copyright (C) 2001-2005 Richard Carlsson
+%%
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% Author contact: [email protected]
+%% =====================================================================
+
+%% Type specification data structures
+
+%% @type t_spec() = #t_spec{name = t_name(),
+%% type = t_type(),
+%% defs = [t_def()]}
+
+-record(t_spec, {name, type, defs=[]}). % function specification
+
+%% @type type() = t_atom() | t_fun() | t_integer() | t_list() | t_nil()
+%% | t_tuple() | t_type() | t_union() | t_var()
+
+%% @type t_typedef() = #t_typedef{name = t_name(),
+%% args = [type()],
+%% type = type(),
+%% defs = [t_def()]}
+
+-record(t_typedef, {name, args, type,
+ defs=[]}). % type declaration/definition
+
+%% @type t_throws() = #t_throws{type = type(),
+%% defs = [t_def()]}
+
+-record(t_throws, {type, defs=[]}). % exception declaration
+
+%% @type t_def() = #t_def{name = t_name(),
+%% type = type()}
+
+-record(t_def, {name, type}). % local definition 'name = type'
+%% @type t_name() = #t_name{app = [] | atom(),
+%% module = [] | atom(),
+%% name = [] | atom()}
+
+-record(t_name, {app = [], % app = [] if module = []
+ module=[], % unqualified if module = []
+ name=[]}).
+
+%% The following records all have 'a=[]' as their first field.
+%% This is used for name and usage annotations; in particular, the
+%% fun-argument types of a function specification (t_spec) are often
+%% annotated with the names of the corresponding formal parameters,
+%% and/or usage summaries.
+
+-define(t_ann(X), element(2, X)).
+-define(set_t_ann(X, Y), setelement(2, X, Y)).
+-define(add_t_ann(X, Y), ?set_t_ann(X, [Y | ?t_ann(X)])).
+
+%% @type t_var() = #t_var{a = list(), name = [] | atom()}
+
+-record(t_var, {a=[], name=[]}). % type variable
+
+%% @type t_type() = #t_type{a = list(),
+%% name = t_name(),
+%% args = [type()]}
+
+-record(t_type, {a=[], name, args = []}). % abstract type 'name(...)'
+
+%% @type t_union() = #t_union{a = list(),
+%% types = [type()]}
+
+-record(t_union, {a=[], types = []}). % union type 't1|...|tN'
+
+%% @type t_fun() = #t_fun{a = list(),
+%% args = [type()],
+%% range = type()}
+
+-record(t_fun, {a=[], args, range}). % function '(t1,...,tN) -> range'
+
+%% @type t_tuple() = #t_tuple{a = list(),
+%% types = [type()]}
+
+-record(t_tuple, {a=[], types = []}). % tuple type '{t1,...,tN}'
+
+%% @type t_list() = #t_list{a = list(),
+%% type = type()}
+
+-record(t_list, {a=[], type}). % list type '[type]'
+
+%% @type t_nil() = #t_nil{a = list()}
+
+-record(t_nil, {a=[]}). % empty-list constant '[]'
+
+%% @type t_atom() = #t_atom{a = list(),
+%% val = atom()}
+
+-record(t_atom, {a=[], val}). % atom constant
+
+%% @type t_integer() = #t_integer{a = list(),
+%% val = integer()}
+
+-record(t_integer, {a=[], val}). % integer constant
+
+%% @type t_float() = #t_float{a = list(),
+%% val = float()}
+
+-record(t_float, {a=[], val}). % floating-point constant
+
+%% @type t_record() = #t_list{a = list(),
+%% name = type(),
+%% fields = [field()]}
+
+-record(t_record, {a=[], name, fields = []}). % record type '#r{f1,...,fN}'
+
+%% @type t_field() = #t_field{a = list(),
+%% name = type(),
+%% type = type()}
+
+-record(t_field, {a=[], name, type}). % named field 'n1=t1'
diff --git a/lib/edoc/src/edoc_wiki.erl b/lib/edoc/src/edoc_wiki.erl
new file mode 100644
index 0000000000..e4a3d74734
--- /dev/null
+++ b/lib/edoc/src/edoc_wiki.erl
@@ -0,0 +1,456 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @private
+%% @copyright 2001-2003 Richard Carlsson
+%% @author Richard Carlsson <[email protected]>
+%% @see edoc
+%% @end
+%% =====================================================================
+
+%% @doc EDoc wiki expansion, parsing and postprocessing of XML text.
+%% Uses {@link //xmerl. XMerL}.
+%% @end
+
+%% Notes:
+%%
+%% * Whatever happens in this module, it must interact nicely with the
+%% actual XML-parsing. It is not acceptable to break any existing and
+%% legal XML markup so that it does not parse or is rendered wrong.
+%%
+%% * The focus should always be on making *documentation* easier to
+%% write. No wiki notation should be introduced unless it is clear that
+%% it is better than using plain XHTML, making typing less cumbersome
+%% and the resulting text easier to read. The wiki notation should be a
+%% small bag of easy-to-remember tricks for making XHTML documentation
+%% easier to write, not a complete markup language in itself. As a
+%% typical example, it is hardly worthwile to introduce a special
+%% notation like say, ""..."" for emphasized text, since <em>...</em> is
+%% not much harder to write, not any less readable, and no more
+%% difficult to remember, especially since emphasis is not very often
+%% occurring in normal documentation.
+%%
+%% * The central reasoning for the code-quoting goes like this: I don't
+%% want to have special escape characters within the quotes (like
+%% backslash in C), to allow quoting of the quote characters themselves.
+%% I also don't want to use the "`" character both for opening and
+%% closing quotes. Therefore, you can either use `...' - and then you
+%% cannot use the "'" character without ending the quote - or you can
+%% use ``...'' - which allows single but not double "'" characters
+%% within the quote. Whitespace is automatically removed from the
+%% beginning and the end of the quoted strings; this allows you to write
+%% things like "`` 'foo@bar' ''". Text that contains "''" has to be
+%% written within <code>...</code>.
+%%
+%% To produce a single "`" character without starting a quote, write
+%% "`'" (no space between "`" and "'").
+%%
+%% For verbatim/preformatted text, the ```...'''-quotes expand to
+%% "<pre><![CDATA[...]]></pre>". The indentation at the start of the
+%% quoted string is preserved; whitespace is stripped only at the end.
+%% Whole leading lines of whitespace are however skipped.
+
+-module(edoc_wiki).
+
+-export([parse_xml/2, expand_text/2]).
+
+-include("edoc.hrl").
+-include("xmerl.hrl").
+
+-define(BASE_HEADING, 3).
+
+
+%% Parsing Wiki-XML with pre-and post-expansion.
+
+parse_xml(Data, Line) ->
+ par(parse_xml_1(expand_text(Data, Line), Line)).
+
+parse_xml_1(Text, Line) ->
+ Text1 = "<doc>" ++ Text ++ "</doc>",
+ case catch {ok, xmerl_scan:string(Text1, [{line, Line}])} of
+ {ok, {E, _}} ->
+ E#xmlElement.content;
+ {'EXIT', {fatal, {Reason, L, _C}}} ->
+ throw_error(L, {"XML parse error: ~p.", [Reason]});
+ {'EXIT', Reason} ->
+ throw_error(Line, {"error in XML parser: ~P.", [Reason, 10]});
+ Other ->
+ throw_error(Line, {"nocatch in XML parser: ~P.", [Other, 10]})
+ end.
+
+%% Expand wiki stuff in arbitrary text.
+
+expand_text(Cs, L) ->
+ lists:reverse(expand_new_line(Cs, L, [])).
+
+%% Interestingly, the reverse of "code" is "edoc". :-)
+
+expand_new_line([$\s = C | Cs], L, As) ->
+ expand_new_line(Cs, L, [C | As]);
+expand_new_line([$\t = C | Cs], L, As) ->
+ expand_new_line(Cs, L, [C | As]);
+expand_new_line([$\n = C | Cs], L, As) ->
+ expand_new_line(Cs, L + 1, [C | As]);
+expand_new_line([$=, $=, $=, $= | Cs], L, As) ->
+ expand_heading(Cs, 2, L, As);
+expand_new_line([$=, $=, $= | Cs], L, As) ->
+ expand_heading(Cs, 1, L, As);
+expand_new_line([$=, $= | Cs], L, As) ->
+ expand_heading(Cs, 0, L, As);
+expand_new_line(Cs, L, As) ->
+ expand(Cs, L, As).
+
+expand([$`, $' | Cs], L, As) ->
+ expand(Cs, L, [$` | As]); % produce "`" - don't start a new quote
+expand([$`, $`, $` | Cs], L, As) ->
+ %% If this is the first thing on the line, compensate for the
+ %% indentation, unless we had to skip one or more empty lines.
+ {Cs1, Skipped} = strip_empty_lines(Cs), % avoid vertical space
+ N = if Skipped > 0 ->
+ 0;
+ true ->
+ {As1, _} = edoc_lib:split_at(As, $\n),
+ case edoc_lib:is_space(As1) of
+ true -> 3 + length(As1);
+ false -> 2 % nice default - usually right.
+ end
+ end,
+ Ss = lists:duplicate(N, $\s),
+ expand_triple(Cs1, L + Skipped, Ss ++ "[ATADC[!<>erp<" ++ As);
+expand([$`, $` | Cs], L, As) ->
+ expand_double(edoc_lib:strip_space(Cs), L, ">edoc<" ++ As);
+expand([$` | Cs], L, As) ->
+ expand_single(edoc_lib:strip_space(Cs), L, ">edoc<" ++ As);
+expand([$[ | Cs], L, As) ->
+ expand_uri(Cs, L, As);
+expand([$\n = C | Cs], L, As) ->
+ expand_new_line(Cs, L + 1, [C | As]);
+expand([C | Cs], L, As) ->
+ expand(Cs, L, [C | As]);
+expand([], _, As) ->
+ As.
+
+%% == Heading ==
+%% === SubHeading ===
+%% ==== SubSubHeading ====
+
+expand_heading([$= | _] = Cs, N, L, As) ->
+ expand_heading_1(Cs, N, L, As);
+expand_heading(Cs, N, L, As) ->
+ {Cs1, Cs2} = edoc_lib:split_at(Cs, $\n),
+ case edoc_lib:strip_space(lists:reverse(Cs1)) of
+ [$=, $= | Cs3] ->
+ {Es, Ts} = lists:splitwith(fun (X) -> X =:= $= end, Cs3),
+ if length(Es) =:= N ->
+ Ts1 = edoc_lib:strip_space(
+ lists:reverse(edoc_lib:strip_space(Ts))),
+ expand_heading_2(Ts1, Cs2, N, L, As);
+ true ->
+ H1 = lists:duplicate(N+2, $=),
+ H2 = "==" ++ Es,
+ throw_error(L, {"heading end marker mismatch: "
+ "~s...~s", [H1, H2]})
+ end;
+ _ ->
+ expand_heading_1(Cs, N, L, As)
+ end.
+
+expand_heading_1(Cs, N, L, As) ->
+ expand(Cs, L, lists:duplicate(N + 2, $=) ++ As).
+
+expand_heading_2(Ts, Cs, N, L, As) ->
+ H = ?BASE_HEADING + N,
+ Ts1 = io_lib:format("<h~w><a name=\"~s\">~s</a></h~w>\n",
+ [H, make_label(Ts), Ts, H]),
+ expand_new_line(Cs, L + 1, lists:reverse(lists:flatten(Ts1), As)).
+
+make_label([$\s | Cs]) ->
+ [$_ | make_label(edoc_lib:strip_space(Cs))];
+make_label([$\t | Cs]) ->
+ [$_ | make_label(edoc_lib:strip_space(Cs))];
+make_label([$\n | Cs]) ->
+ [$_ | make_label(edoc_lib:strip_space(Cs))];
+make_label([C | Cs]) ->
+ [C | make_label(Cs)];
+make_label([]) ->
+ [].
+
+%% `...'
+
+expand_single(Cs, L, As) ->
+ expand_single(Cs, L, As, L).
+
+expand_single([$' | Cs], L, As, _L0) ->
+ expand(Cs, L, ">edoc/<" ++ edoc_lib:strip_space(As));
+expand_single([$< | Cs], L, As, L0) ->
+ expand_single(Cs, L, ";tl&" ++ As, L0);
+expand_single([$> | Cs], L, As, L0) ->
+ expand_single(Cs, L, ";tg&" ++ As, L0);
+expand_single([$& | Cs], L, As, L0) ->
+ expand_single(Cs, L, ";pma&" ++ As, L0);
+expand_single([$\n = C | Cs], L, As, L0) ->
+ expand_single(Cs, L + 1, [C | As], L0);
+expand_single([C | Cs], L, As, L0) ->
+ expand_single(Cs, L, [C | As], L0);
+expand_single([], L, _, L0) ->
+ throw_error(L0, {"`-quote ended unexpectedly at line ~w", [L]}).
+
+%% ``...''
+
+expand_double(Cs, L, As) ->
+ expand_double(Cs, L, As, L).
+
+expand_double([$', $' | Cs], L, As, _L0) ->
+ expand(Cs, L, ">edoc/<" ++ edoc_lib:strip_space(As));
+expand_double([$< | Cs], L, As, L0) ->
+ expand_double(Cs, L, ";tl&" ++ As, L0);
+expand_double([$> | Cs], L, As, L0) ->
+ expand_double(Cs, L, ";tg&" ++ As, L0);
+expand_double([$& | Cs], L, As, L0) ->
+ expand_double(Cs, L, ";pma&" ++ As, L0);
+expand_double([$\n = C | Cs], L, As, L0) ->
+ expand_double(Cs, L + 1, [C | As], L0);
+expand_double([C | Cs], L, As, L0) ->
+ expand_double(Cs, L, [C | As], L0);
+expand_double([], L, _, L0) ->
+ throw_error(L0, {"``-quote ended unexpectedly at line ~w", [L]}).
+
+%% ```...'''
+
+expand_triple(Cs, L, As) ->
+ expand_triple(Cs, L, As, L).
+
+expand_triple([$', $', $' | Cs], L, As, _L0) -> % ' stupid emacs
+ expand(Cs, L, ">erp/<>]]" ++ edoc_lib:strip_space(As));
+expand_triple([$], $], $> | Cs], L, As, L0) ->
+ expand_triple(Cs, L, ";tg&]]" ++ As, L0);
+expand_triple([$\n = C | Cs], L, As, L0) ->
+ expand_triple(Cs, L + 1, [C | As], L0);
+expand_triple([C | Cs], L, As, L0) ->
+ expand_triple(Cs, L, [C | As], L0);
+expand_triple([], L, _, L0) ->
+ throw_error(L0, {"```-quote ended unexpectedly at line ~w", [L]}).
+
+%% e.g. [file:/...] or [http://... LinkText]
+
+expand_uri("http:/" ++ Cs, L, As) ->
+ expand_uri(Cs, L, "/:ptth", As);
+expand_uri("ftp:/" ++ Cs, L, As) ->
+ expand_uri(Cs, L, "/:ptf", As);
+expand_uri("file:/" ++ Cs, L, As) ->
+ expand_uri(Cs, L, "/:elif", As);
+expand_uri(Cs, L, As) ->
+ expand(Cs, L, [$[ | As]).
+
+expand_uri([$] | Cs], L, Us, As) ->
+ expand(Cs, L, push_uri(Us, ">tt/<" ++ Us ++ ">tt<", As));
+expand_uri([$\s = C | Cs], L, Us, As) ->
+ expand_uri(Cs, 0, L, [C], Us, As);
+expand_uri([$\t = C | Cs], L, Us, As) ->
+ expand_uri(Cs, 0, L, [C], Us, As);
+expand_uri([$\n = C | Cs], L, Us, As) ->
+ expand_uri(Cs, 1, L, [C], Us, As);
+expand_uri([C | Cs], L, Us, As) ->
+ expand_uri(Cs, L, [C | Us], As);
+expand_uri([], L, Us, _As) ->
+ expand_uri_error(Us, L).
+
+expand_uri([$] | Cs], N, L, Ss, Us, As) ->
+ Ss1 = lists:reverse(edoc_lib:strip_space(
+ lists:reverse(edoc_lib:strip_space(Ss)))),
+ expand(Cs, L + N, push_uri(Us, Ss1, As));
+expand_uri([$\n = C | Cs], N, L, Ss, Us, As) ->
+ expand_uri(Cs, N + 1, L, [C | Ss], Us, As);
+expand_uri([C | Cs], N, L, Ss, Us, As) ->
+ expand_uri(Cs, N, L, [C | Ss], Us, As);
+expand_uri([], _, L, _Ss, Us, _As) ->
+ expand_uri_error(Us, L).
+
+-spec expand_uri_error(list(), pos_integer()) -> no_return().
+
+expand_uri_error(Us, L) ->
+ {Ps, _} = edoc_lib:split_at(lists:reverse(Us), $:),
+ throw_error(L, {"reference '[~s:...' ended unexpectedly", [Ps]}).
+
+
+push_uri(Us, Ss, As) ->
+ ">a/<" ++ Ss ++ ">\"pot_\"=tegrat \"" ++ Us ++ "\"=ferh a<" ++ As.
+
+
+strip_empty_lines(Cs) ->
+ strip_empty_lines(Cs, 0).
+
+strip_empty_lines(Cs, N) ->
+ {Cs1, Cs2} = edoc_lib:split_at(Cs, $\n),
+ case edoc_lib:is_space(Cs1) of
+ true ->
+ strip_empty_lines(Cs2, N + 1);
+ false ->
+ {Cs, N}
+ end.
+
+
+%% Scanning element content for paragraph breaks (empty lines).
+%% Paragraphs are flushed by block level elements.
+
+par(Es) ->
+ par(Es, [], []).
+
+par([E=#xmlText{value = Value} | Es], As, Bs) ->
+ par_text(Value, As, Bs, E, Es);
+par([E=#xmlElement{name = Name} | Es], As, Bs) ->
+ %% (Note that paragraphs may not contain any further block-level
+ %% elements, including other paragraphs. Tables get complicated.)
+ case Name of
+ 'p' -> par_flush(Es, [E | As], Bs);
+ 'hr' -> par_flush(Es, [E | As], Bs);
+ 'h1' -> par_flush(Es, [E | As], Bs);
+ 'h2' -> par_flush(Es, [E | As], Bs);
+ 'h3' -> par_flush(Es, [E | As], Bs);
+ 'h4' -> par_flush(Es, [E | As], Bs);
+ 'h5' -> par_flush(Es, [E | As], Bs);
+ 'h6' -> par_flush(Es, [E | As], Bs);
+ 'pre' -> par_flush(Es, [E | As], Bs);
+ 'address' -> par_flush(Es, [E | As], Bs);
+ 'div' -> par_flush(Es, [par_elem(E) | As], Bs);
+ 'blockquote' -> par_flush(Es, [par_elem(E) | As], Bs);
+ 'form' -> par_flush(Es, [par_elem(E) | As], Bs);
+ 'fieldset' -> par_flush(Es, [par_elem(E) | As], Bs);
+ 'noscript' -> par_flush(Es, [par_elem(E) | As], Bs);
+ 'ul' -> par_flush(Es, [par_subelem(E) | As], Bs);
+ 'ol' -> par_flush(Es, [par_subelem(E) | As], Bs);
+ 'dl' -> par_flush(Es, [par_subelem(E) | As], Bs);
+ 'table' -> par_flush(Es, [par_subelem(E) | As], Bs);
+ _ -> par(Es, [E | As], Bs)
+ end;
+par([E | Es], As, Bs) ->
+ par(Es, [E | As], Bs);
+par([], As, Bs) ->
+ lists:reverse(As ++ Bs).
+
+par_text(Cs, As, Bs, E, Es) ->
+ case ptxt(Cs) of
+ none ->
+ %% no blank lines: keep this element as it is
+ par(Es, [E | As], Bs);
+ {Cs1, Ss, Cs2} ->
+ Es1 = case Cs1 of
+ [] -> lists:reverse(As);
+ _ -> lists:reverse(As, [E#xmlText{value = Cs1}])
+ end,
+ Bs0 = case Es1 of
+ [] -> Bs;
+ _ -> [#xmlElement{name = p, content = Es1} | Bs]
+ end,
+ Bs1 = case Ss of
+ [] -> Bs0;
+ _ -> [#xmlText{value = Ss} | Bs0]
+ end,
+ case Cs2 of
+ [] ->
+ par(Es, [], Bs1);
+ _ ->
+ par_text(Cs2, [], Bs1, #xmlText{value = Cs2}, Es)
+ end
+ end.
+
+par_flush(Es, As, Bs) ->
+ par(Es, [], As ++ Bs).
+
+par_elem(E) ->
+ E#xmlElement{content = par(E#xmlElement.content)}.
+
+%% Only process content of subelements; ignore immediate content.
+par_subelem(E) ->
+ E#xmlElement{content = par_subelem_1(E#xmlElement.content)}.
+
+par_subelem_1([E=#xmlElement{name = Name} | Es]) ->
+ E1 = case par_skip(Name) of
+ true ->
+ E;
+ false ->
+ case par_sub(Name) of
+ true ->
+ par_subelem(E);
+ false ->
+ par_elem(E)
+ end
+ end,
+ [E1 | par_subelem_1(Es)];
+par_subelem_1([E | Es]) ->
+ [E | par_subelem_1(Es)];
+par_subelem_1([]) ->
+ [].
+
+par_skip('caption') -> true;
+par_skip('col') -> true;
+par_skip('colgroup') -> true;
+par_skip(_) -> false.
+
+par_sub(tr) -> true;
+par_sub(thead) -> true;
+par_sub(tfoot) -> true;
+par_sub(tbody) -> true;
+par_sub(_) -> false.
+
+
+%% scanning text content for a blank line
+
+ptxt(Cs) ->
+ ptxt(Cs, []).
+
+ptxt([$\n | Cs], As) ->
+ ptxt_1(Cs, As, [$\n]);
+ptxt([C | Cs], As) ->
+ ptxt(Cs, [C | As]);
+ptxt([], _As) ->
+ none.
+
+%% scanning text following an initial newline
+ptxt_1([C=$\s | Cs], As, Ss) ->
+ ptxt_1(Cs, As, [C | Ss]);
+ptxt_1([C=$\t | Cs], As, Ss) ->
+ ptxt_1(Cs, As, [C | Ss]);
+ptxt_1([C=$\n | Cs], As, Ss) ->
+ %% blank line detected
+ ptxt_2(Cs, As, [C | Ss]);
+ptxt_1(Cs, As, Ss) ->
+ %% not a blank line
+ ptxt(Cs, lists:reverse(Ss, As)).
+
+%% collecting whitespace following a blank line
+ptxt_2([C=$\s | Cs], As, Ss) ->
+ ptxt_2(Cs, As, [C | Ss]);
+ptxt_2([C=$\t | Cs], As, Ss) ->
+ ptxt_2(Cs, As, [C | Ss]);
+ptxt_2([C=$\n | Cs], As, Ss) ->
+ ptxt_2(Cs, As, [C | Ss]);
+ptxt_2(Cs, As, Ss) ->
+ %% ended by non-whitespace or end of element
+ case edoc_lib:is_space(As) of
+ true ->
+ {[], lists:reverse(Ss ++ As), Cs};
+ false ->
+ {lists:reverse(As), lists:reverse(Ss), Cs}
+ end.
+
+
+-spec throw_error(non_neg_integer(), {string(), [_]}) -> no_return().
+
+throw_error(L, D) ->
+ throw({error, L, D}).
diff --git a/lib/edoc/src/otpsgml_layout.erl b/lib/edoc/src/otpsgml_layout.erl
new file mode 100644
index 0000000000..45f74b299e
--- /dev/null
+++ b/lib/edoc/src/otpsgml_layout.erl
@@ -0,0 +1,853 @@
+%% =====================================================================
+%% This library is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU Lesser General Public License as
+%% published by the Free Software Foundation; either version 2 of the
+%% License, or (at your option) any later version.
+%%
+%% This library is distributed in the hope that it will be useful, but
+%% WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%% Lesser General Public License for more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public
+%% License along with this library; if not, write to the Free Software
+%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+%% USA
+%%
+%% $Id$
+%%
+%% @author Richard Carlsson <[email protected]>
+%% @author Kenneth Lundin <[email protected]>
+%% @copyright 2001-2004 Richard Carlsson
+%% @see edoc_layout
+%% @end
+%% =====================================================================
+
+%% @doc The OTP SGML layout module for EDoc. See the module {@link edoc}
+%% for details on usage.
+
+%% Note that this is written so that it is *not* depending on edoc.hrl!
+
+-module(otpsgml_layout).
+
+-export([module/2, package/2, overview/2,type/1]).
+
+-import(edoc_report, [report/2]).
+
+-include("xmerl.hrl").
+
+-define(SGML_EXPORT, xmerl_otpsgml).
+-define(DEFAULT_XML_EXPORT, ?SGML_EXPORT).
+-define(STYLESHEET, "stylesheet.css").
+-define(NL, "\n").
+-define(DESCRIPTION_TITLE, "Description").
+-define(DESCRIPTION_LABEL, "description").
+-define(DATA_TYPES_TITLE, "Data Types").
+-define(DATA_TYPES_LABEL, "types").
+-define(FUNCTION_INDEX_TITLE, "Function Index").
+-define(FUNCTION_INDEX_LABEL, "index").
+-define(FUNCTIONS_TITLE, "Function Details").
+-define(FUNCTIONS_LABEL, "functions").
+
+
+%% @doc The layout function.
+%%
+%% Options:
+%% <dl>
+%% <dt>{@type {index_columns, integer()@}}
+%% </dt>
+%% <dd>Specifies the number of column pairs used for the function
+%% index tables. The default value is 1.
+%% </dd>
+%% <dt>{@type {stylesheet, string()@}}
+%% </dt>
+%% <dd>Specifies the URI used for referencing the stylesheet. The
+%% default value is `"stylesheet.css"'. If an empty string is
+%% specified, no stylesheet reference will be generated.
+%% </dd>
+%% <dt>{@type {xml_export, Module::atom()@}}
+%% </dt>
+%% <dd>Specifies an {@link //xmerl. `xmerl'} callback module to be
+%% used for exporting the documentation. See {@link
+%% //xmerl/xmerl:export_simple/3} for details.
+%% </dd>
+%% </dl>
+%%
+%% @see edoc:layout/2
+
+-record(opts, {root, stylesheet, index_columns}).
+
+module(Element, Options) ->
+ XML = layout_module(Element, init_opts(Element, Options)),
+ Export = proplists:get_value(xml_export, Options,
+ ?DEFAULT_XML_EXPORT),
+ xmerl:export_simple([XML], Export, []).
+
+% Put layout options in a data structure for easier access.
+
+init_opts(Element, Options) ->
+ R = #opts{root = get_attrval(root, Element),
+ index_columns = proplists:get_value(index_columns,
+ Options, 1)
+ },
+ case proplists:get_value(stylesheet, Options) of
+ undefined ->
+ S = edoc_lib:join_uri(R#opts.root, ?STYLESHEET),
+ R#opts{stylesheet = S};
+ "" ->
+ R; % don't use any stylesheet
+ S when is_list(S) ->
+ R#opts{stylesheet = S};
+ _ ->
+ report("bad value for option `stylesheet'.", []),
+ exit(error)
+ end.
+
+
+%% =====================================================================
+%% XML-BASED LAYOUT ENGINE
+%% =====================================================================
+
+%% We assume that we have expanded XML data.
+
+%% <!ELEMENT module (moduleName, moduleFullName, behaviour*, description?,
+%% author*, version?, since?, copyright?, deprecated?,
+%% see*, reference*, typedecls?, functions)>
+%% <!ATTLIST module
+%% root CDATA #IMPLIED>
+%% <!ELEMENT moduleName (#PCDATA)>
+%% <!ELEMENT moduleFullName (#PCDATA)>
+%% <!ELEMENT behaviour (#PCDATA)>
+%% <!ATTLIST behaviour
+%% href CDATA #IMPLIED>
+%% <!ELEMENT description (briefDescription, fullDescription?)>
+%% <!ELEMENT briefDescription (#PCDATA)>
+%% <!ELEMENT fullDescription (#PCDATA)>
+%% <!ELEMENT author EMPTY>
+%% <!ATTLIST author
+%% name CDATA #REQUIRED
+%% email CDATA #IMPLIED
+%% website CDATA #IMPLIED>
+%% <!ELEMENT version (#PCDATA)>
+%% <!ELEMENT since (#PCDATA)>
+%% <!ELEMENT copyright (#PCDATA)>
+%% <!ELEMENT deprecated (description)>
+%% <!ELEMENT see (#PCDATA)>
+%% <!ATTLIST see
+%% name CDATA #REQUIRED
+%% href CDATA #IMPLIED>
+%% <!ELEMENT reference (#PCDATA)>
+%% <!ELEMENT typedecls (typedecl+)>
+%% <!ELEMENT functions (function+)>
+
+layout_module(#xmlElement{name = module, content = Es}=E, _Opts) ->
+ Name = get_attrval(name, E),
+ Desc = get_content(description, Es),
+ ShortDesc = get_content(briefDescription, Desc),
+ FullDesc = get_content(fullDescription, Desc),
+ Functions = [E || E <- get_content(functions, Es)],
+ SortedFs = lists:sort([{function_name(E), E} || E <- Functions]),
+ Types = get_content(typedecls, Es),
+ SortedTs = lists:sort([{type_name(E), E} || E <- Types]),
+ Header = {header, [
+ ?NL,{title, [Name]},
+ ?NL,{prepared, [""]},
+ ?NL,{responsible, [""]},
+ ?NL,{docno, ["1"]},
+ ?NL,{approved, [""]},
+ ?NL,{checked, [""]},
+ ?NL,{date, [""]},
+ ?NL,{rev, ["A"]},
+ ?NL,{file, [Name++".sgml"]}
+ ]},
+ Module = {module, [Name]},
+ ModuleSummary = {modulesummary, ShortDesc},
+ {Short,Long} = find_first_p(FullDesc,[]),
+ Description = {description, [?NL,{p,Short}|Long]++[?NL|types(SortedTs)]},
+ Funcs = functions(SortedFs),
+ Authors = {authors, authors(Es)},
+ See = sees1(Es),
+ {erlref, [
+ ?NL,Header,
+ ?NL,Module,
+ ?NL,ModuleSummary,
+ ?NL,Description,
+ ?NL,Funcs,
+ ?NL,See,
+ ?NL,Authors
+ ]
+ }.
+
+stylesheet(Opts) ->
+ case Opts#opts.stylesheet of
+ undefined ->
+ [];
+ CSS ->
+ [{link, [{rel, "stylesheet"},
+ {type, "text/css"},
+ {href, CSS}], []},
+ ?NL]
+ end.
+
+% doc_index(FullDesc, Functions, Types) ->
+% case doc_index_rows(FullDesc, Functions, Types) of
+% [] -> [];
+% Rs ->
+% [{ul, [{li, [{a, [{href, local_label(R)}], [T]}]}
+% || {T, R} <- Rs]}]
+% end.
+
+% doc_index_rows(FullDesc, Functions, Types) ->
+% (if FullDesc == [] -> [];
+% true -> [{?DESCRIPTION_TITLE, ?DESCRIPTION_LABEL}]
+% end
+% ++ if Types == [] -> [];
+% true -> [{?DATA_TYPES_TITLE, ?DATA_TYPES_LABEL}]
+% end
+% ++ if Functions == [] -> [];
+% true -> [{?FUNCTION_INDEX_TITLE, ?FUNCTION_INDEX_LABEL},
+% {?FUNCTIONS_TITLE, ?FUNCTIONS_LABEL}]
+% end).
+
+% function_index(Fs, Cols) ->
+% case function_index_rows(Fs, Cols, []) of
+% [] -> [];
+% Rows ->
+% [?NL,
+% {h2, [{a, [{name, ?FUNCTION_INDEX_LABEL}],
+% [?FUNCTION_INDEX_TITLE]}]},
+% ?NL,
+% {table, [{width, "100%"}, {border, 1}], Rows},
+% ?NL]
+% end.
+
+% function_index_rows(Fs, Cols, Title) ->
+% Rows = (length(Fs) + (Cols - 1)) div Cols,
+% (if Title == [] -> [];
+% true -> [{tr, [{th, [{colspan, Cols * 2}, {align, left}],
+% [Title]}]},
+% ?NL]
+% end
+% ++ lists:flatmap(fun index_row/1,
+% edoc_lib:transpose(edoc_lib:segment(Fs, Rows)))).
+
+% index_row(Fs) ->
+% [{tr, lists:flatmap(fun index_col/1, Fs)}, ?NL].
+
+% index_col({Name, F=#xmlElement{content = Es}}) ->
+% [{td, [{valign, "top"}], label_href([Name], F)},
+% {td, index_desc(Es)}].
+
+index_desc(Es) ->
+ Desc = get_content(description, Es),
+ case get_content(briefDescription, Desc) of
+ [] ->
+ equiv(Es); % no description at all if no equiv
+ ShortDesc ->
+ ShortDesc
+ end.
+
+% label_href(Content, F) ->
+% case get_attrval(label, F) of
+% "" -> Content;
+% Ref -> [{a, [{href, local_label(Ref)}], Content}]
+% end.
+
+
+%% <!ELEMENT function (args, typespec?, equiv?, description?, since?,
+%% deprecated?, see*)>
+%% <!ATTLIST function
+%% name CDATA #REQUIRED
+%% arity CDATA #REQUIRED
+%% exported NMTOKEN(yes | no) #REQUIRED
+%% label CDATA #IMPLIED>
+%% <!ELEMENT args (arg*)>
+%% <!ELEMENT arg description?>
+%% <!ATTLIST arg name CDATA #REQUIRED>
+
+
+%% <!ELEMENT equiv (expr, see?)>
+%% <!ELEMENT expr (#PCDATA)>
+
+% functions(Fs) ->
+% Es = lists:flatmap(fun ({Name, E}) -> function(Name, E) end, Fs),
+% if Es == [] -> [];
+% true ->
+% [?NL,
+% {h2, [{a, [{name, ?FUNCTIONS_LABEL}], [?FUNCTIONS_TITLE]}]},
+% ?NL | Es]
+% end.
+
+functions(Fs) ->
+ Es = lists:flatmap(fun ({Name, E}) -> function(Name, E) end, Fs),
+ if Es == [] -> [];
+ true ->
+ {funcs, Es}
+ end.
+
+% is_exported(E) ->
+% case get_attrval(exported, E) of
+% "yes" -> true;
+% _ -> false
+% end.
+
+% function(Name, E=#xmlElement{content = Es}) ->
+% ([?NL, {h3, label_anchor([Name], E)}, ?NL]
+% ++ case typespec(get_content(typespec, Es)) of
+% [] ->
+% signature(get_content(arguments, Es),
+% get_text(functionName, Es));
+% Spec -> Spec
+% end
+% ++ equiv(Es)
+% ++ deprecated(Es, "function")
+% ++ fulldesc(Es)
+% ++ since(Es)
+% ++ sees(Es)).
+
+function(_Name, E=#xmlElement{content = Es}) ->
+ TypeSpec = get_content(typespec, Es),
+ [?NL,{func, [ ?NL,
+ {name,
+% case typespec(get_content(typespec, Es)) of
+ case funcheader(TypeSpec) of
+ [] ->
+ signature(get_content(args, Es),
+ get_attrval(name, E));
+ Spec -> Spec
+ end
+ },
+ ?NL,{fsummary, fsummary(Es)},
+% ?NL,{type, local_types(TypeSpec)},
+ ?NL,local_types(TypeSpec),
+ ?NL,{desc, label_anchor(E)++fulldesc(Es)++sees(Es)}
+ ]}].
+
+fsummary([]) -> ["\s"];
+fsummary(Es) ->
+ Desc = get_content(description, Es),
+ case get_content(briefDescription, Desc) of
+ [] ->
+ fsummary_equiv(Es); % no description at all if no equiv
+ ShortDesc ->
+ ShortDesc
+ end.
+
+
+fsummary_equiv(Es) ->
+ case get_content(equiv, Es) of
+ [] -> ["\s"];
+ Es1 ->
+ case get_content(expr, Es1) of
+ [] -> ["\s"];
+ [Expr] ->
+ ["Equivalent to ", Expr, ".",?NL]
+ end
+ end.
+
+
+function_name(E) ->
+ get_attrval(name, E) ++ "/" ++ get_attrval(arity, E).
+
+label_anchor(E) ->
+ case get_attrval(label, E) of
+ "" -> [];
+ Ref -> [{marker, [{id, Ref}],[]},?NL]
+ end.
+
+label_anchor(Content, E) ->
+ case get_attrval(label, E) of
+ "" -> Content;
+ Ref -> {p,[{marker, [{id, Ref}],[]},
+ {em, Content}]}
+ end.
+
+%% <!ELEMENT args (arg*)>
+%% <!ELEMENT arg (argName, description?)>
+%% <!ELEMENT argName (#PCDATA)>
+
+%% This is currently only done for functions without type spec.
+
+signature(Es, Name) ->
+% [{tt, [Name, "("] ++ seq(fun arg/1, Es) ++ [") -> term()", ?NL]}].
+ [Name, "("] ++ seq(fun arg/1, Es) ++ [") -> term()", ?NL].
+
+arg(#xmlElement{content = Es}) ->
+ [get_text(argName, Es)].
+
+%% <!ELEMENT typespec (erlangName, type, localdef*)>
+
+% typespec([]) -> [];
+% typespec(Es) ->
+% [{p, ([{tt, ([t_name(get_elem(qualifiedName, Es))]
+% ++ t_type(get_content(type, Es)))}]
+% ++ local_defs(get_elem(definition, Es)))},
+% ?NL].
+
+funcheader([]) -> [];
+funcheader(Es) ->
+ [t_name(get_elem(erlangName, Es))] ++ t_utype(get_elem(type, Es)).
+
+local_types([]) -> [];
+local_types(Es) ->
+ local_defs2(get_elem(localdef, Es)).
+
+local_defs2([]) -> [];
+local_defs2(Es) ->
+ {type,[?NL | [{v, localdef(E)} || E <- Es]]}.
+
+%% <!ELEMENT typedecl (typedef, description?)>
+%% <!ELEMENT typedef (erlangName, argtypes, type?, localdef*)>
+
+types([]) -> [];
+types(Ts) ->
+ Es = lists:flatmap(fun ({Name, E}) -> typedecl(Name, E) end, Ts),
+ [?NL,
+% {h2, [{a, [{name, ?DATA_TYPES_LABEL}],
+% [?DATA_TYPES_TITLE]}]},
+% ?NL | Es]
+ {p,[{marker, [{id, ?DATA_TYPES_LABEL}],[]},
+ {em,[?DATA_TYPES_TITLE]}]},
+ ?NL, {taglist,[?NL|Es]}].
+
+%%type(Name, E=#xmlElement{content = Es}) ->
+%% ([?NL, {h3, label_anchor([Name, "()"], E)}, ?NL]
+%% ++ [{p, typedef(get_content(typedef, Es))}, ?NL]
+%% ++ fulldesc(Es)).
+typedecl(_Name, #xmlElement{content = Es}) ->
+ [{tag, typedef(get_content(typedef, Es))},?NL,{item,fulldesc(Es)},?NL].
+
+
+type_name(#xmlElement{content = Es}) ->
+ t_name(get_elem(erlangName, get_content(typedef, Es))).
+
+typedef(Es) ->
+ Name = ([t_name(get_elem(erlangName, Es)), "("]
+ ++ seq(fun t_utype_elem/1, get_content(argtypes, Es), [")"])),
+ (case get_elem(type, Es) of
+ [] -> [{b, ["abstract datatype"]}, ": ", {tt, Name}];
+ Type ->
+ [{tt, Name ++ [" = "] ++ t_utype(Type)}]
+ end
+ ++ local_defs(get_elem(localdef, Es))).
+
+local_defs([]) -> [];
+local_defs(Es) ->
+ [?NL, {ul, [{li, [{tt, localdef(E)}]} || E <- Es]}].
+
+localdef(E = #xmlElement{content = Es}) ->
+ (case get_elem(typevar, Es) of
+ [] ->
+ label_anchor(t_abstype(get_content(abstype, Es)), E);
+ [V] ->
+ t_var(V)
+ end
+ ++ [" = "] ++ t_utype(get_elem(type, Es))).
+
+fulldesc(Es) ->
+ case get_content(fullDescription, get_content(description, Es)) of
+% [] -> [?NL];
+ [] -> index_desc(Es);
+% Desc -> [{p, Desc}, ?NL]
+ Desc ->
+ {Short,Long} = find_first_p(Desc,[]),
+ [?NL,{p,Short}|Long] ++[?NL]
+ end.
+
+find_first_p([#xmlElement{name=p}|_]=Long,Short) ->
+ {lists:reverse(Short),Long};
+find_first_p([H|T],Short) ->
+ find_first_p(T,[H|Short]);
+find_first_p([],Short) ->
+ {lists:reverse(Short),[]}.
+
+
+sees1(Es) ->
+ case get_elem(see, Es) of
+ [] -> [];
+ Es1 ->
+ {section,[{title,["See also"]},{p,seq(fun see/1, Es1, [])}]}
+ end.
+
+sees(Es) ->
+ case get_elem(see, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{em, ["See also:"]}, " "] ++ seq(fun see/1, Es1, ["."])},
+ ?NL]
+ end.
+
+see(E=#xmlElement{content = Es}) ->
+ see(E,Es).
+
+see(E, Es) ->
+ case get_attrval(href, E) of
+ "" -> Es;
+ Ref ->
+ case lists:reverse(Ref) of
+ "lmgs.ppa_"++Ppa ->
+ App = lists:reverse(Ppa),
+ [{seealso, [{marker, App++"_app"}], [App]},"(6)"];
+ "lmgs."++Dom ->
+ Mod = lists:reverse(Dom),
+ [{seealso, [{marker, Mod}], [Mod]},"(3)"];
+ _ ->
+ [{seealso, [{marker, Ref}], Es}]
+ end
+ end.
+
+equiv(Es) ->
+ case get_content(equiv, Es) of
+ [] -> ["\s"];
+ Es1 ->
+ case get_content(expr, Es1) of
+ [] -> [];
+ [Expr] ->
+% Expr1 = {tt, [Expr]},
+% Expr1 = {c, [Expr]},
+ Expr1 = [Expr],
+ Expr2 = case get_elem(see, Es1) of
+ [] ->
+ {c,Expr1};
+ [E=#xmlElement{}] ->
+% see(E,Expr1)
+ case get_attrval(href, E) of
+ "" ->
+ {c,Expr1};
+ Ref ->
+ {seealso, [{marker, Ref}], Expr1}
+ end
+ end,
+ [{p, ["Equivalent to ", Expr2, "."]}, ?NL]
+ end
+ end.
+
+% replace_minus_with_percent([$-|T]) ->
+% [$%|T];
+% replace_minus_with_percent([H|T]) ->
+% [H|replace_minus_with_percent(T)].
+
+copyright(Es) ->
+ case get_content(copyright, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, ["Copyright \251 " | Es1]}, ?NL]
+ end.
+
+version(Es) ->
+ case get_content(version, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{b, ["Version:"]}, " " | Es1]}, ?NL]
+ end.
+
+since(Es) ->
+ case get_content(since, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{b, ["Introduced in:"]}, " " | Es1]}, ?NL]
+ end.
+
+deprecated(Es, S) ->
+ Es1 = get_content(description, get_content(deprecated, Es)),
+ case get_content(fullDescription, Es1) of
+ [] -> [];
+ Es2 ->
+ [{p, [{b, ["This " ++ S ++ " is deprecated:"]}, " " | Es2]},
+ ?NL]
+ end.
+
+% behaviours(Es) ->
+% case get_elem(behaviour, Es) of
+% [] -> [];
+% Es1 ->
+% [{p, [{b, ["Behaviour:"]}, " "] ++ seq(fun behaviour/1, Es1, ["."])},
+% ?NL]
+% end.
+
+% behaviour(E=#xmlElement{content = Es}) ->
+% case get_attrval(href, E) of
+% "" -> [{tt, Es}];
+% Ref -> [{a, [{href, Ref}], [{tt, Es}]}]
+% end.
+
+authors(Es) ->
+ case get_elem(author, Es) of
+ [] -> [?NL,{aname,["\s"]},?NL,{email,["\s"]}];
+ Es1 -> [?NL|seq(fun author/1, Es1, [])]
+%
+% [{p, [{b, ["Authors:"]}, " "] ++ seq(fun author/1, Es1, ["."])},
+% ?NL]
+ end.
+
+
+%% <!ATTLIST author
+%% name CDATA #REQUIRED
+%% email CDATA #IMPLIED
+%% website CDATA #IMPLIED>
+
+author(E=#xmlElement{}) ->
+ Name = case get_attrval(name, E) of
+ [] -> "\s";
+ N -> N
+ end,
+ Mail = case get_attrval(email, E) of
+ [] -> "\s";
+ M -> M
+ end,
+ [?NL,{aname,[Name]},?NL,{email,[Mail]}].
+
+% author(E=#xmlElement{}) ->
+% Name = get_attrval(name, E),
+% Mail = get_attrval(email, E),
+% URI = get_attrval(website, E),
+% (if Name == Mail ->
+% [{a, [{href, "mailto:" ++ Mail}],[{tt, [Mail]}]}];
+% true ->
+% if Mail == "" -> [Name];
+% true -> [Name, " (", {a, [{href, "mailto:" ++ Mail}],
+% [{tt, [Mail]}]}, ")"]
+% end
+% end
+% ++ if URI == "" -> [];
+% true -> [" [", {em, ["web site:"]}, " ",
+% {tt, [{a, [{href, URI}], [URI]}]}, "]"]
+% end).
+
+references(Es) ->
+ case get_elem(reference, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{b, ["References"]},
+ {ul, [{li, C} || #xmlElement{content = C} <- Es1]}]},
+ ?NL]
+ end.
+
+t_name([E]) ->
+ N = get_attrval(name, E),
+ case get_attrval(module, E) of
+ "" -> N;
+ M ->
+ S = M ++ ":" ++ N,
+ case get_attrval(app, E) of
+ "" -> S;
+ A -> "//" ++ A ++ "/" ++ S
+ end
+ end.
+
+t_utype([E]) ->
+ t_utype_elem(E).
+
+t_utype_elem(E=#xmlElement{content = Es}) ->
+ case get_attrval(name, E) of
+ "" -> t_type(Es);
+ Name ->
+ T = t_type(Es),
+ case T of
+ [Name] -> T; % avoid generating "Foo::Foo"
+ T -> [Name] ++ ["::"] ++ T
+ end
+ end.
+
+t_type([E=#xmlElement{name = typevar}]) ->
+ t_var(E);
+t_type([E=#xmlElement{name = atom}]) ->
+ t_atom(E);
+t_type([E=#xmlElement{name = integer}]) ->
+ t_integer(E);
+t_type([E=#xmlElement{name = float}]) ->
+ t_float(E);
+t_type([#xmlElement{name = nil}]) ->
+ t_nil();
+t_type([#xmlElement{name = list, content = Es}]) ->
+ t_list(Es);
+t_type([#xmlElement{name = tuple, content = Es}]) ->
+ t_tuple(Es);
+t_type([#xmlElement{name = 'fun', content = Es}]) ->
+ t_fun(Es);
+t_type([E = #xmlElement{name = abstype, content = Es}]) ->
+ T = t_abstype(Es),
+% see(E,T);
+ case get_attrval(href, E) of
+ "" -> T;
+ % Ref -> [{seealso, [{marker, Ref}], T}]
+ _Ref -> T
+ end;
+t_type([#xmlElement{name = union, content = Es}]) ->
+ t_union(Es).
+
+t_var(E) ->
+ [get_attrval(name, E)].
+
+
+t_atom(E) ->
+ [get_attrval(value, E)].
+
+t_integer(E) ->
+ [get_attrval(value, E)].
+
+t_float(E) ->
+ [get_attrval(value, E)].
+
+t_nil() ->
+ ["[]"].
+
+t_list(Es) ->
+ ["["] ++ t_utype(get_elem(type, Es)) ++ ["]"].
+
+t_tuple(Es) ->
+ ["{"] ++ seq(fun t_utype_elem/1, Es, ["}"]).
+
+t_fun(Es) ->
+ ["("] ++ seq(fun t_utype_elem/1, get_content(argtypes, Es),
+ [") -> "] ++ t_utype(get_elem(type, Es))).
+
+t_abstype(Es) ->
+% ([t_name(get_elem(qualifiedName, Es)), "("]
+% ++ seq(fun t_type_elem/1, get_elem(type, Es), [")"])).
+ case split_at_colon(t_name(get_elem(erlangName, Es)),[]) of
+ {Mod,Type} ->
+ [Type, "("] ++
+ seq(fun t_utype_elem/1, get_elem(type, Es), [")"]) ++
+ [" (see module ", Mod, ")"];
+ Type ->
+ [Type, "("] ++
+ seq(fun t_utype_elem/1, get_elem(type, Es), [")"])
+ end.
+
+%% Split at one colon, but not at two (or more)
+split_at_colon([$:,$:|_]=Rest,Acc) ->
+ lists:reverse(Acc)++Rest;
+split_at_colon([$:|Type],Acc) ->
+ {lists:reverse(Acc),Type};
+split_at_colon([Char|Rest],Acc) ->
+ split_at_colon(Rest,[Char|Acc]);
+split_at_colon([],Acc) ->
+ lists:reverse(Acc).
+
+% t_par(Es) ->
+% T = t_type(get_content(type, Es)),
+% case get_elem(variable, Es) of
+% [] -> T;
+% [V0] -> case t_variable(V0) of
+% T -> T;
+% V -> V ++ ["::"] ++ T
+% end
+% end.
+
+% t_par_elem(#xmlElement{content = Es}) -> t_par(Es).
+
+t_union(Es) ->
+ seq(fun t_utype_elem/1, Es, " | ", []).
+
+seq(F, Es) ->
+ seq(F, Es, []).
+
+seq(F, Es, Tail) ->
+ seq(F, Es, ", ", Tail).
+
+seq(F, [E], _Sep, Tail) ->
+ F(E) ++ Tail;
+seq(F, [E | Es], Sep, Tail) ->
+ F(E) ++ [Sep] ++ seq(F, Es, Sep, Tail);
+seq(_F, [], _Sep, Tail) ->
+ Tail.
+
+get_elem(Name, [#xmlElement{name = Name} = E | Es]) ->
+ [E | get_elem(Name, Es)];
+get_elem(Name, [_ | Es]) ->
+ get_elem(Name, Es);
+get_elem(_, []) ->
+ [].
+
+get_attr(Name, [#xmlAttribute{name = Name} = A | As]) ->
+ [A | get_attr(Name, As)];
+get_attr(Name, [_ | As]) ->
+ get_attr(Name, As);
+get_attr(_, []) ->
+ [].
+
+get_attrval(Name, #xmlElement{attributes = As}) ->
+ case get_attr(Name, As) of
+ [#xmlAttribute{value = V}] ->
+ V;
+ [] -> ""
+ end.
+
+get_content(Name, Es) ->
+ case get_elem(Name, Es) of
+ [#xmlElement{content = Es1}] ->
+ Es1;
+ [] -> []
+ end.
+
+get_text(Name, Es) ->
+ case get_content(Name, Es) of
+ [#xmlText{value = Text}] ->
+ Text;
+ [] -> ""
+ end.
+
+% local_label(R) ->
+% "#" ++ R.
+
+xml(Title, CSS, Body) ->
+ {html, [?NL,
+ {head, [?NL,
+ {title, [Title]},
+ ?NL] ++ CSS},
+ ?NL,
+ {body, [{bgcolor, "white"}], Body},
+ ?NL]
+ }.
+
+%% ---------------------------------------------------------------------
+
+ type(E) ->
+ type(E, []).
+
+% type(E, Ds) ->
+% xmerl:export_simple_content(t_utype_elem(E) ++ local_defs(Ds),
+% ?HTML_EXPORT).
+ type(E, Ds) ->
+ xmerl:export_simple_content(t_utype_elem(E) ++ local_defs(Ds),
+ ?SGML_EXPORT).
+
+
+package(E=#xmlElement{name = package, content = Es}, Options) ->
+ Opts = init_opts(E, Options),
+ Name = get_text(packageName, Es),
+ Title = io_lib:fwrite("Package ~s", [Name]),
+ Desc = get_content(description, Es),
+% ShortDesc = get_content(briefDescription, Desc),
+ FullDesc = get_content(fullDescription, Desc),
+ Body = ([?NL, {h1, [Title]}, ?NL]
+% ++ ShortDesc
+ ++ copyright(Es)
+ ++ deprecated(Es, "package")
+ ++ version(Es)
+ ++ since(Es)
+ ++ authors(Es)
+ ++ references(Es)
+ ++ sees(Es)
+ ++ FullDesc),
+ XML = xml(Title, stylesheet(Opts), Body),
+ xmerl:export_simple([XML], ?SGML_EXPORT, []).
+
+overview(E=#xmlElement{name = overview, content = Es}, Options) ->
+ Opts = init_opts(E, Options),
+ Title = get_text(title, Es),
+ Desc = get_content(description, Es),
+% ShortDesc = get_content(briefDescription, Desc),
+ FullDesc = get_content(fullDescription, Desc),
+ Body = ([?NL, {h1, [Title]}, ?NL]
+% ++ ShortDesc
+ ++ copyright(Es)
+ ++ version(Es)
+ ++ since(Es)
+ ++ authors(Es)
+ ++ references(Es)
+ ++ sees(Es)
+ ++ FullDesc),
+ XML = xml(Title, stylesheet(Opts), Body),
+ xmerl:export_simple([XML], ?SGML_EXPORT, []).