diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/edoc | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/edoc')
51 files changed, 12037 insertions, 0 deletions
diff --git a/lib/edoc/COPYING b/lib/edoc/COPYING new file mode 100644 index 0000000000..223ede7de3 --- /dev/null +++ b/lib/edoc/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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 + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/lib/edoc/Makefile b/lib/edoc/Makefile new file mode 100644 index 0000000000..2b011b55cc --- /dev/null +++ b/lib/edoc/Makefile @@ -0,0 +1,127 @@ +# ``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$ +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# +# Macros +# + +SUB_DIRECTORIES = src include priv doc/src + +include vsn.mk +VSN = $(EDOC_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/xmerl-$(VSN) + +# ---------------------------------------------------- +# Help application directory specification +# ---------------------------------------------------- +DIR_NAME = edoc-$(VSN) +ERL_DIR = src + +ifndef APP_RELEASE_DIR + APP_RELEASE_DIR = /tmp +endif + +# ---------------------------------------------------- + +EXTRA_FILES = \ + edoc-info overview.edoc stylesheet.css + +EXTRA_HTML_FILES = \ + modules-frame.html overview-summary.html \ + packages-frame.html + +HTML_FILES = doc/*.html + +# ---------------------------------------------------- +ifndef APP_TAR_FILE + APP_TAR_FILE = $(APP_RELEASE_DIR)/$(DIR_NAME).tgz +endif + +APP_DIR = $(APP_RELEASE_DIR)/$(DIR_NAME) + + +SPECIAL_TARGETS = + +# +# Default Subdir Targets +# + +include $(ERL_TOP)/make/otp_subdir.mk + + +.PHONY: info version + + +version: + @echo "$(VSN)" + + +APPNAME=edoc +BINDIR=$(ERL_TOP)/lib/edoc/ebin +DOCDIR=$(ERL_TOP)/lib/edoc/doc +DOC_OPTS=[{def,{version,"$(VSN)"}},todo] + +SYNTAX_TOOLS_DIR=$(ERL_TOP)/lib/syntax_tools +XMERL_DIR=$(ERL_TOP)/lib/xmerl +INCDIR=$(XMERL_DIR)/include + +docs: + erl -noshell -pa $(BINDIR) -pa $(SYNTAX_TOOLS_DIR)/ebin \ + -pa $(XMERL_DIR)/ebin -run edoc_run application \ + "'$(APPNAME)'" '"."' '$(DOC_OPTS)' + +edocs: docs + +info: + @echo $(HTML_FILES) + + +app_release: tar + +app_dir: $(APP_DIR) + + +$(APP_DIR): + cat TAR.exclude > TAR.exclude2; \ + echo "edoc/TAR.exclude2" >> TAR.exclude2; \ + (cd ..; find edoc -name 'findmerge.*' >> edoc/TAR.exclude2) + (cd ..; find edoc -name '*.contrib*' >> edoc/TAR.exclude2) + (cd ..; find edoc -name '*.keep*' >> edoc/TAR.exclude2) + (cd ..; find edoc -name '*~' >> edoc/TAR.exclude2) + (cd ..; find edoc -name 'erl_crash.dump' >> edoc/TAR.exclude2) + (cd ..; find edoc -name '*.log' >> edoc/TAR.exclude2) + (cd ..; find edoc -name 'core' >> edoc/TAR.exclude2) + (cd ..; find edoc -name '.cmake.state' >> edoc/TAR.exclude2) + mkdir $(APP_DIR); \ + (cd ..; tar cfX - edoc/TAR.exclude2 edoc) | \ + (cd $(APP_DIR); tar xf -); \ + mv $(APP_DIR)/edoc/* $(APP_DIR)/; \ + rmdir $(APP_DIR)/edoc + mkdir $(APP_DIR)/doc; \ + (cd doc; tar cf - man3 html) | (cd $(APP_DIR)/doc; tar xf -) + +tar: $(APP_TAR_FILE) + +$(APP_TAR_FILE): $(APP_DIR) + (cd $(APP_RELEASE_DIR); gtar zcf $(APP_TAR_FILE) $(DIR_NAME)) diff --git a/lib/edoc/doc/Makefile b/lib/edoc/doc/Makefile new file mode 100644 index 0000000000..a0f6484382 --- /dev/null +++ b/lib/edoc/doc/Makefile @@ -0,0 +1,91 @@ +# ``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: Makefile,v 1.1.1.1 2004/10/04 13:53:33 richardc Exp $ +# +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) + +# ---------------------------------------------------- +# Help application directory specification +# ---------------------------------------------------- + +APPNAME=edoc +DOC_TITLE="Welcome to EDoc" + +HTML_FILES = *.html +INFO_FILE = ../info +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + + +docs: + (cd ..; \ + edoc_generate -app '$(APPNAME)' -vsn '$(VSN)') + + +info: + @echo "HTML_FILES:" $(HTML_FILES) + @echo "HTMLDIR: $(HTMLDIR)" + + + +debug opt: + + +clean: + rm -f $(HTML_FILES) stylesheet.css edoc-info + rm -f errs core *~ + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- + + +include $(ERL_TOP)/make/otp_release_targets.mk + +release_docs_spec: docs + $(INSTALL_DIR) $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(HTML_FILES) $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR) + + +release_spec: + + + +# ---------------------------------------------------- +# Include dependency +# ---------------------------------------------------- +#-include make.dep + + diff --git a/lib/edoc/doc/html/.gitignore b/lib/edoc/doc/html/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/edoc/doc/html/.gitignore diff --git a/lib/edoc/doc/man3/.gitignore b/lib/edoc/doc/man3/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/edoc/doc/man3/.gitignore diff --git a/lib/edoc/doc/overview.edoc b/lib/edoc/doc/overview.edoc new file mode 100644 index 0000000000..9b25c17b1f --- /dev/null +++ b/lib/edoc/doc/overview.edoc @@ -0,0 +1,1026 @@ + -*- html -*- + + EDoc overview page + + +@author Richard Carlsson <[email protected]> +@copyright 2003-2006 Richard Carlsson +@version {@version} +@title Welcome to EDoc + +@doc EDoc is the Erlang program documentation generator. Inspired by the +Javadoc<sup><font size="-3">TM</font></sup> tool for the Java<sup><font +size="-3">TM</font></sup> programming language, EDoc is adapted to the +conventions of the Erlang world, and has several features not found in +Javadoc. + +== Contents == + +<ol> + <li>{@section Introduction}</li> + <li>{@section Running EDoc}</li> + <li>{@section The overview page}</li> + <li>{@section Generic tags}</li> + <li>{@section Overview tags}</li> + <li>{@section Module tags}</li> + <li>{@section Function tags}</li> + <li>{@section References}</li> + <li>{@section Notes on XHTML}</li> + <li>{@section Wiki notation}</li> + <li>{@section Macro expansion}</li> + <li>{@section Type specifications}</li> + <li>{@section Acknowledgements}</li> +</ol> + +== Introduction == + +EDoc lets you write the documentation of an Erlang program as +comments in the source code itself, using <em>tags</em> on the form +"`@Name ...'". A source file does not have to contain tags +for EDoc to generate its documentation, but without tags the result will +only contain the basic available information that can be extracted from +the module. + +A tag must be the first thing on a comment line, except for leading +'`%'' characters and whitespace. The comment must be between +program declarations, and not on the same line as any program text. All +the following text - including consecutive comment lines - up until the +end of the comment or the next tagged line, is taken as the +<em>content</em> of the tag. + +Tags are associated with the nearest following program construct "of +significance" (the module name declaration and function +definitions). Other constructs are ignored; e.g., in: +``` + %% @doc Prints the value X. + + -record(foo, {x, y, z}). + + print(X) -> ... +''' +the `@doc' tag is associated with the function `print/1'. + +Note that in a comment such as: +```% % @doc ...''' +the tag is ignored, because only the first '`%'' character is +considered "leading". This allows tags to be "commented out". + +Some tags, such as `@type', do not need to be associated +with any program construct. These may be placed at the end of the file, +in the "footer". + + +== Running EDoc == + +The following are the main functions for running EDoc: + <ul> + <li>{@link edoc:application/2}: Creates documentation for a + typical Erlang application.</li> + <li>{@link edoc:packages/2}: Creates documentation for one or + more packages, automatically locating source files.</li> + <li>{@link edoc:files/2}: Creates documentation for a + specified set of source files.</li> + <li>{@link edoc:run/3}: General interface function; the common + back-end for the above functions. Options are documented here.</li> + </ul> + +Note that the function {@link edoc:file/2} belongs to the old, deprecated +interface (from EDoc version 0.1), and should not be used. + + +== The overview page == + +When documentation is generated for an entire application, an overview +page, or "front page", is generated. (The page you are now reading is an +overview page.) This should contain the high-level description or user +manual for the application, leaving the finer details to the +documentation for individual modules. By default, the overview page is +generated from the file `overview.edoc' in the target directory +(typically, this is the `doc' subdirectory of the application +directory); see {@link edoc_doclet} for details. + +The format of the overview file is the same as for EDoc documentation +comments (see {@section Introduction}), except that the lines do not +have leading '`%'' characters. Furthermore, all lines before the first +tag line are ignored, and can be used as a comment. All tags in the +overview file, such as `@@doc', `@@version', etc., refer to the +application as a whole; see {@section Overview tags} for details. + +Here is an example of the contents of an overview file: +```** this is the overview.doc file for the application 'frob' ** + + @@author R. J. Hacker <[email protected]> + @@copyright 2007 R. J. Hacker + @@version 1.0.0 + @@title Welcome to the `frob' application! + @@doc `frob' is a highly advanced frobnicator with low latency, + ... +''' + + +== Generic tags == + +The following tags can be used anywhere within a module: +<dl> + <dt><a name="gtag-clear">`@clear'</a></dt> + + <dd>This tag causes all tags above it (up to the previous program + construct), to be discarded, including the `@clear' + tag itself. The text following the tag + is also ignored. <em>This is typically only useful in code + containing conditional compilation, when preprocessing is turned + on.</em> (Preprocessing is turned off by default.) E.g., in +```-ifdef(DEBUG). + %% @doc ... + foo(...) -> ... + -endif. + %% @clear + + %% @doc ... + bar(...) -> ...''' + the `@clear' tag makes sure that EDoc does not see + two `@doc' tags before the function `bar', + even if the code for function `foo' is removed by + preprocessing. (There is no way for EDoc to see what the first + `@doc' tag "really" belongs to, since preprocessing + strips away all such information.)</dd> + + <dt><a name="gtag-docfile">`@docfile'</a></dt> + <dd>Reads a plain documentation file (on the same format as an + overview file - see {@section The overview page} for details), and + uses the tags in that file as if they had been written in place of + the `@docfile' tag. The content is the name of the file to be + read; leading and trailing whitespace is ignored. See also <a + href="#gtag-headerfile">`@headerfile'</a>.</dd> + + <dt><a name="gtag-end">`@end'</a></dt> + <dd>The text following this tag is always ignored. Use this to + mark the end of the previous tag, when necessary, as e.g. in: +```%% ---------------------------------- + %% ... + %% @doc ... + %% ... + %% @end + %% ----------------------------------''' + to avoid including the last "ruler" line in the + `@doc' tag. + + <em>Note: using some other "dummy" `@'-tag for + the same purpose might work in a particular implementation of + EDoc, but is not guaranteed to. Always use `@end' + to ensure future compatibility.</em></dd> + + <dt><a name="gtag-headerfile">`@headerfile'</a></dt> + <dd>Similar to the <a href="#gtag-docfile">`@docfile' tag</a>, but + reads a file containing Erlang source code - generally this should + be a header file (with the extension `.hrl'). If the file turns + out to contain one or more function definitions or a module + declaration, all tags that occur above the last such definition or + module declaration are ignored, and EDoc will print a + warning. This tag allows you to write documentation in a header + file and insert it at a specific place in the documentation, even + if the header file is used (i.e., included) by several + modules. The `includes' option can be used to specify a search + path (see {@link edoc:read_source/2}).</dd> + + <dt><a name="gtag-todo">`@todo' (or `@TODO')</a></dt> + <dd>Attaches a To-Do note to a function, module, package, or + overview-page. The content can be any XHTML text describing + the issue, e.g.: +```%% @TODO Finish writing the documentation.''' + or +```%% @todo Implement <a href="http://www.ietf.org/rfc/rfc2549.txt">RFC 2549</a>.''' + These tags can also be written as "`TODO:'", e.g.: +```%% TODO: call your mother''' + see {@section Wiki notation} for more information. To-Do notes are + normally not shown unless the `todo' option is turned on (see + {@link edoc:get_doc/2}).</dd> + + <dt><a name="gtag-type">`@type'</a></dt> + <dd>Documents an abstract data type or type alias. The content + consists of a type declaration or definition, optionally + followed by a period ('`.'') separator and XHTML + text describing the type (i.e., its purpose, use, etc.). There + must be at least one whitespace character between the '`.'' and + the text. See {@section Type specifications} for syntax and + examples. + All data type descriptions are placed in a separate section of + the documentation, regardless of where the tags occur.</dd> + +</dl> + + +== Overview tags == + +The following tags can be used in an overview file. +<dl> + <dt><a name="otag-author">`@author'</a></dt> + <dd>See the <a href="#mtag-author">`@author' module tag</a> for + details.</dd> + + <dt><a name="otag-copyright">`@copyright'</a></dt> + <dd>See the <a href="#mtag-copyright">`@copyright' module tag</a> + for details.</dd> + + <dt><a name="otag-doc">`@doc'</a></dt> + <dd>See the <a href="#mtag-doc">`@doc' module tag</a> for + details.</dd> + + <dt><a name="otag-reference">`@reference'</a></dt> + <dd>See the <a href="#mtag-reference">`@reference' module tag</a> + for details.</dd> + + <dt><a name="otag-see">`@see'</a></dt> + <dd>See the <a href="#mtag-see">`@see' module tag</a> for + details.</dd> + + <dt><a name="otag-since">`@since'</a></dt> + <dd>See the <a href="#mtag-since">`@since' module tag</a> for + details.</dd> + + <dt><a name="otag-title">`@title'</a></dt> + <dd>Specifies a title for the overview page. This tag can + <em>only</em> be used in an overview file. The content can be + arbitrary text.</dd> + + <dt><a name="otag-version">`@version'</a></dt> + <dd>See the <a href="#mtag-version">`@version' module + tag</a> for details.</dd> + +</dl> + + +== Module tags == + +The following tags can be used before a module declaration: +<dl> + <dt><a name="mtag-author">`@author'</a></dt> + <dd>Specifies the name of an author, along with contact + information. An e-mail address can be given within `<...>' + delimiters, and a URI within `[...]' delimiters. Both e-mail and + URI are optional, and any surrounding whitespace is stripped from + all strings. + + The name is the first nonempty string that is not within `<...>' + or `[...]', and does not contain only whitespace. (In other words, + the name can come before, between, or after the e-mail and URI, + but cannot be split up; any sections after the first are ignored.) + If an e-mail address is given, but no name, the e-mail string will + be used also for the name. If no `<...>' section is present, but + the name string contains an '`@'' character, it is assumed to be + an e-mail address. Not both name and e-mail may be left out. + + Examples: +```%% @author Richard Carlsson''' + +```%% @author Richard Carlsson <[email protected]> + %% [http://user.it.uu.se/~richardc/]''' + +```%% @author <[email protected]>''' + +```%% @author [email protected] [http://user.it.uu.se/~richardc/]''' + </dd> + +<dt><a name="mtag-copyright">`@copyright'</a></dt> + <dd>Specifies the module copyrights. The content can be + arbitrary text; for example: +``` + %% @copyright 2001-2003 Richard Carlsson''' + </dd> + + <dt><a name="mtag-deprecated">`@deprecated'</a></dt> + <dd>Mark the module as deprecated, indicating that it should no + longer be used. The content must be well-formed XHTML, and should + preferably include a `@{@link}' reference to a + replacement; as in: +``` + %% @deprecated Please use the module @{@link foo} instead.''' + </dd> + + <dt><a name="mtag-doc">`@doc'</a></dt> + <dd>Describes the module, using well-formed XHTML text. The + first sentence is used as a summary (see the + <a href="#ftag-doc">`@doc' function tag</a> for details). For + example.: +```%% @doc This is a <em>very</em> useful module. It is ...'''</dd> + + <dt><a name="mtag-hidden">`@hidden'</a></dt> + <dd>Marks the module so that it will not appear in the + documentation (even if "private" documentation is generated). + Useful for sample code, test modules, etc. The content can be + used as a comment; it is ignored by EDoc.</dd> + + <dt><a name="mtag-private">`@private'</a></dt> + <dd>Marks the module as private (i.e., not part of the public + interface), so that it will not appear in the normal + documentation. (If "private" documentation is generated, the + module will be included.) The content can be used as a comment; it + is ignored by EDoc.</dd> + + <dt><a name="mtag-reference">`@reference'</a></dt> + <dd>Specifies a reference to some arbitrary external resource, + such as an article, book, or web site. The content must be + well-formed XHTML text. Examples: +```%% @reference Pratchett, T., <em>Interesting Times</em>, + %% Victor Gollancz Ltd, 1994.''' + +```%% @reference See <a href="www.google.com">Google</a> for + %% more information.''' + </dd> + + <dt><a name="mtag-see">`@see'</a></dt> + <dd>See the <a href="#ftag-see">`@see' function tag</a> + for details.</dd> + + <dt><a name="mtag-since">`@since'</a></dt> + <dd>Specifies when the module was introduced, with respect to + the application, package, release or distribution it is part + of. The content can be arbitrary text.</dd> + + <dt><a name="mtag-version">`@version'</a></dt> + <dd>Specifies the module version. The content can be arbitrary + text.</dd> + +</dl> + + + +== Function tags == + +The following tags can be used before a function definition: +<dl> + <dt><a name="ftag-deprecated">`@deprecated'</a></dt> + <dd>See the <a href="#mtag-deprecated">`@deprecated' + module tag</a> for details.</dd> + + <dt><a name="ftag-doc">`@doc'</a></dt> + <dd>XHTML text describing the function. The first + sentence of the text is used as a quick summary; this ends at + the first period character ('`.'') or exclamation mark + ('`!'') that is followed by a whitespace character, a + line break, or the end of the tag text, and is not within XML + markup. (As an exception, the first sentence may be within an + initial paragraph element)</dd> + + <dt><a name="ftag-equiv">`@equiv'</a></dt> + <dd>Specify equivalence to another function call/expression. + The content must be a proper Erlang expression. If the + expression is a function call, a cross-reference to the called + function is created automatically. Typically, this tag is used + instead of `@doc'. </dd> + + <dt><a name="ftag-hidden">`@hidden'</a></dt> + <dd>Marks the function so that it will not appear in the + documentation (even if "private" documentation is generated). + Useful for debug/test functions, etc. The content can be + used as a comment; it is ignored by EDoc.</dd> + + <dt><a name="ftag-private">`@private'</a></dt> + <dd>Marks the function as private (i.e., not part of the public + interface), so that it will not appear in the normal + documentation. (If "private" documentation is generated, the + function will be included.) Only useful for exported functions, + e.g. entry points for `spawn'. (Non-exported functions are + always "private".) The content can be used as a comment; it is + ignored by EDoc.</dd> + + <dt><a name="ftag-see">`@see'</a></dt> + <dd>Make a reference to a module, function, datatype, or + application. (See {@section References}.) + The content consists of a reference, optionally followed by a + period ('`.''), one or more whitespace characters, and + XHTML text to be used for the label; for example "`@see edoc'" or + "`@see edoc. <b>EDoc</b>'". If no label text is specified, the + reference itself is used as the label.</dd> + + <dt><a name="ftag-since">`@since'</a></dt> + <dd>Specifies in what version of the module the function was + introduced; cf. the + <a href="#mtag-version">`@version' + module tag</a>. The content can be arbitrary text.</dd> + + <dt><a name="ftag-spec">`@spec'</a></dt> + <dd>Used to specify the function type; see {@section Type + specifications} for syntax details. If the function name is + included in the specification, it must match the name in the + actual code. When parameter names are not given in the + specification, suitable names will be taken from the source + code if possible, and otherwise synthesized.</dd> + + <dt><a name="ftag-throws">`@throws'</a></dt> + <dd>Specifies which types of terms may be thrown by the + function, if its execution terminates abruptly due to a call to + `erlang:throw(Term)'. The content is a type expression (see {@section + Type specifications}), and can be a union type. + + Note that exceptions of type `exit' (as caused by calls to + `erlang:exit(Term)') and `error' (run-time errors such as `badarg' + or `badarith') are not viewed as part of the normal interface of + the function, and cannot be documented with the `@throws' tag.</dd> + + <dt><a name="ftag-type">`@type'</a></dt> + <dd>See the <a href="#gtag-type">`@type' generic tag</a> + for details. Placing a `@type' tag by a function + definition may be convenient, but does not affect where the + description is placed in the generated documentation.</dd> +</dl> + + + +== References == + +In several contexts (`@see' tags, `@link' macros, etc.), EDoc lets +you refer to the generated documentation for modules, functions, +datatypes, and applications, using a simple and compact syntax. The +possible formats for references are: +<table border="1" summary="reference syntax"> + <tr><th>Reference syntax</th><th>Example</th><th>Scope</th></tr> + <tr><td>`Module'</td><td>{@link edoc_run}, `erl.lang.list'</td><td>Global</td></tr> + <tr><td>`Package.*'</td><td>`erl.lang.*'</td><td>Global</td></tr> + <tr><td>`Function/Arity'</td><td>`file/2'</td><td>Within module</td></tr> + <tr><td>`Module:Function/Arity'</td><td>{@link edoc:application/2}</td><td>Global</td></tr> + <tr><td>`Type()'</td><td>`filename()'</td><td>Within module</td></tr> + <tr><td>`Module:Type()'</td><td>{@link edoc:edoc_module()}</td><td>Global</td></tr> + <tr><td>`//Application'</td><td>{@link //edoc}</td><td>Global</td></tr> + <tr><td>`//Application/Module'</td><td>{@link //edoc/edoc_doclet}</td><td>Global</td></tr> + <tr><td>`//Application/Module:Function/Arity'</td><td>{@link //edoc/edoc_run:file/1}</td><td>Global</td></tr> + <tr><td>`//Application/Module:Type()'</td><td>{@link //edoc/edoc:edoc_module()}</td><td>Global</td></tr> +</table> + + +EDoc will resolve references using the information it finds in +`edoc-info'-files at the locations specified with the `doc_path' +option. EDoc will automatically (and somewhat intelligently) try to find +any local `edoc-info'-files using the current code path, and add them to +the end of the `doc_path' list. The target doc-directory is also +searched for an existing info file; this allows documentation to be +built incrementally. (Use the `new' option to ignore any old info +file.) + +Note that if the name of a module, function or datatype is explicitly +qualified with an application (as in "`//edoc/edoc_run'"), this +overrides any other information about that name, and the reference will +be made relative to the location of the application (if it can be +found). This makes it possible to refer to e.g. a module "`fred'" as +"`//foo/fred'" without accidentally getting a reference to +"`//bar/fred'". You should not use this form of explicit references for +names that are local to the application you are currently creating - +they will always be resolved correctly. + +Note that module-local references such as `file/2' only work properly +within a module. In an overview-page like this (i.e., the one you are +currently reading), no module context is available. + +== Notes on XHTML == + +In several places, XHTML markup can be used in the documentation +text, in particular in `@doc' tags. The main differences from +HTML are the following: +<ul> + <li>All elements must have explicit start and end tags, and be + correctly nested. This means that you cannot e.g. write a + `<li>' tag without also writing a corresponding `</li>' + tag in the right place. This could be an annoyance + at times, but has the great advantage that EDoc can report all + malformed XHTML in your source code, rather than propagate the + errors to the generated documentation.</li> + <li>XHTML tag and attribute names should always be lower-case.</li> + <li>Attributes must be quoted, as in e.g. `<a + name="top">'.</li> +</ul> +To write an element like the HTML `<br>', which has no actual content, +you can write either the full `<br></br>', or better, use the XHTML +abbreviated form `<br/>'. + +Since the purpose of EDoc is to document programs, there is also a +limited form of "wiki"-syntax available for making program code easier +to write inline (and to make the doc-comments easier to read). +See {@section Wiki notation} for details. + +The HTML heading tags `h1' and `h2' are reserved for use by EDoc. +Headings in documentation source code should start at `h3'. There is +however a special syntax for writing headings which avoids using +specific level numbers altogether; see {@section Headings} for details. + +EDoc uses {@link //xmerl. XMerL} to parse and export XML markup. + + +== Wiki notation == + +When EDoc parses XHTML, it does additional pre- and post-processing of +the text in order to expand certain notation specific to EDoc into +proper XHTML markup. This "wiki" ([http://en.wikipedia.org/wiki/Wiki]) +notation is intended to make it easier to write source code +documentation. + + === Empty lines separate paragraphs === + +Leaving an empty line in XHTML text (i.e., a line which except for +any leading start-of-comment '<tt>%</tt>' characters contains only +whitespace), will make EDoc split the text before and +after the empty line into separate paragraphs. For example: +```%% @doc This will all be part of the first paragraph. + %% It can stretch over several lines and contain <em>any + %% XHTML markup</em>. + %% + %% This is the second paragraph. The above line is + %% regarded as "empty" by EDoc, even though it ends with + %% a space.''' +will generate the following text: +<blockquote><p>This will all be part of the first paragraph. It can +stretch over several lines and contain <em>any XHTML markup</em>.</p> +This is the second paragraph. The above line is regarded as "empty" by +EDoc, even though it ends with a space.</blockquote> + +Paragraph splitting takes place after the actual XHTML parsing. It only +affects block-level text, and not e.g., text within `<pre>' markup, or +text that is already within `<p>' markup. + + === Headings === + +Section headings, sub-headings, and sub-sub-headings, can be written +using the following notation: +```== Heading == + === Sub-heading === + ==== Sub-sub-heading ====''' +Such a heading must be alone on a line, except for whitespace, and +cannot be split over several lines. A link target is automatically +created for the heading, by replacing any whitespace within the text by +a single underscore character. E.g., +```== Concerning Hobbits ==''' +is equivalent to +```<h3><a name="Concerning_Hobbits">Concerning Hobbits</a></h3>''' +Thus, headings using this notation should not contain characters that +may not be part of URL labels, except for whitespace. If you need to +create such headings, you have to use the explicit XHTML markup. + +A hypertext link to a heading written this way can be created using the +`@section' macro, which transforms the argument text into a label as +described above. E.g., +```@{@section Concerning Hobbits}''' +is equivalent to writing +```<a href="#Concerning_Hobbits">Concerning Hobbits</a>''' + +The above expansions take place before XML parsing. + + === External links === + +Writing a URL within brackets, as in "`[http://www.w3c.org/]'", will +generate a hyperlink such as [http://www.w3c.org/], using the URL both +for the destination and the label of the reference, equivalent to writing +"`<a href="http://www.w3c.org/"><tt>http://www.w3c.org/</tt></a>'". This +short-hand keeps external URL references short and readable. The +recognized protocols are `http', `ftp', and `file'. This expansion takes +place before XML parsing. + + === TODO-notes === + +Lines that begin with the text "`TODO:'" (the colon is required) are +recognized as tags, as if they had been written as "`@todo ...'" (see <a +href="#gtag-todo">@todo tags</a> for further details). + + === Verbatim quoting === + +In XHTML text, the '<code>`</code>' character (Unicode `000060', +known as "grave accent" or "back-quote") can be used for verbatim +quoting. This expansion takes place before XML parsing. + +<ul> + <li>A character sequence "<code>`...'</code>" or + "<code>``...''</code>" will be expanded to + "`<code>...</code>'", where all occurrences of the special XML + characters '`<'' and '`&'' (and for completeness, also '`>'') in + the quoted text have been escaped to "`<'", "`&'", and + "`>'", respectively. + All whitespace is stripped from the beginning and end of the + quoted text. + + Double back-quotes "<code>``...''</code>" can be used + to quote text containing single '`` ' ''' characters. The automatic + stripping of any surrounding whitespace makes it possible to write + things like "<code>`` 'foo@bar' ''</code>". + + To quote text containing "<code>''</code>" verbatim, + explicit `<code>' markup or similar must be used.</li> + + <li>A character sequence "<code>```...'''</code>" + will be expanded to "`<pre><![CDATA[...]]></pre>'", which disables + all XML markup within the quoted text, and displays the result in + fixed-font with preserved indentation. Whitespace is stripped from + the end of the quoted text, but not from the beginning, except for + whole leading lines of whitespace. This is + useful for multi-line code examples, or displayed + one-liners.</li> + + <li>To produce a single '<code>`</code>'-character in XML + without beginning a new quote, you can write "<code>`'</code>" + (no space between the '<code>`</code>' and the '<code>'</code>'). + You can of course also use the XML character entity + "``'".</li> +</ul> + +Examples: + ```%% @doc ...where the variable `Foo' refers to... ''' + + ```%% @doc ...returns the atom `` '[email protected]' ''... ''' + + <pre> + %% @doc ...use the command ```erl -name foo''' to...</pre> + + <pre> + %% @doc ...as in the following code: + %% ```f(X) -> + %% case X of + %% ... + %% end'''</pre> + + <pre> + %% @doc ...or in the following: + %% ``` + %% g(X) -> + %% fun () -> ... end + %% '''</pre> + + +== Macro expansion == + +Before the content of a tag is parsed, the text undergoes <em>macro +expansion</em>. The syntax for macro calls is: +<pre> + @{@<em>name</em>}</pre> +or +<pre> + @{@<em>name</em> <em>argument</em>}</pre> +where <em>name</em> and <em>argument</em> are separated by one or more +whitespace characters. The argument can be any text, which may contain +other macro calls. The number of non-escaped "<code>@{@</code>" and +"`}'" delimiters must be balanced. + + The argument text is first expanded in the current environment, and +the result is bound to the <em>macro parameter</em>, written +<code>@{@?}</code>. (If no argument is given, <code>@{@?}</code> is +bound to the empty string.) The macro definition is then substituted +for the call, and expansion continues over the resulting text. Recursive +macro expansions are not allowed. + + === User-defined macros === + +Users can define their own macros by using the `def' EDoc +option; see {@link edoc:file/2} and {@link edoc:get_doc/2} for more +information. User-defined macros override predefined macros. + + === Predefined macros === + +<dl> + <dt><a name="predefmacro-date"><code>@{@date}</code></a></dt> + <dd>Expands to the current date, as "<tt>Month Day Year</tt>", + e.g. "{@date}".</dd> + + <dt><a name="predefmacro-docRoot"><code>@{@docRoot}</code></a></dt> + <dd>Expands to the relative URL path (such as + `"../../.."') from the current page to the root + directory of the generated documentation. This can be used to + create XHTML references such as `<img + src="@{@docRoot}/images/logo.jpeg">' that are independent of how + deep down in a package structure they occur. If packages are not + used (i.e., if all modules are in the "empty" package), + <code>@{@docRoot}</code> will always resolve to the empty + string.</dd> + + <dt><a name="predefmacro-link"><code>@{@link <em>reference</em>. + <em>description</em>}</code></a></dt> + <dd>This creates a hypertext link; cf. the + <a href="#ftag-see">`@see' function tag</a> above for + details. The description text (including the period separator) + is optional; if no text is given, the reference itself is + used. For example, <code>@{@link edoc:file/2}</code> creates the + link {@link edoc:file/2}, and `@{@link edoc:file/2. <em>this link</em>}' + creates {@link edoc:file/2. <em>this link</em>}.</dd> + + <dt><a name="predefmacro-module"><code>@{@module}</code></a></dt> + <dd>Expands to the name of the current module. Only defined when a + module is being processed.</dd> + + <dt><a name="predefmacro-package"><code>@{@package}</code></a></dt> + <dd>Expands to the name of the current package.</dd> + + <dt><a name="predefmacro-section"><code>@{@section + <em>heading</em>}</code></a></dt> + <dd>Expands to a hypertext link to the specified section heading; + see {@section Headings} for more information.</dd> + + <dt><a name="predefmacro-time"><code>@{@time}</code></a></dt> + <dd>Expands to the current time, as "<tt>Hr:Min:Sec</tt>", + e.g. "{@time}".</dd> + + <dt><a name="predefmacro-type"><code>@{@type + <em>type-expression</em>}</code></a></dt> + <dd>Formats a type expression within `<code>...</code>' + markup and with hypertext links for data types. For example, + <code>@{@type {options, List::edoc:option_list()@@}}</code> + generates "{@type {options, List::edoc:option_list()@}}". (Cf. + {@section Escape sequences}.)</dd> + + <dt><a name="predefmacro-version"><code>@{@version}</code></a></dt> + <dd>Intended for use in <a href="#mtag-version">`@version' + tags</a>. Defaults to a timestamp using `@{@date}' and `@{@time}'. + Typically, this macro is redefined by the user when an official + release of the application is generated.</dd> +</dl> + + === Escape sequences === + +To prevent certain characters from being interpreted as delimiters, +for example to produce the text "<code>@{@</code>" in the output, or use a +'`}'' character in the argument text of a macro call, the +following escape sequences may be used: <dl> + <dt><code>@@{</code></dt> + <dd>Expands to "`{'". Example: +``` + %% @doc A macro call starts with the sequence "@@@{@".''' + </dd> + <dt><code>@@}</code></dt> + <dd>Expands to "`}'". Example: +``` + %% @doc ...@{@foo ...{Key, Value@@}...}...''' + </dd> + <dt><code>@@@@</code></dt> + <dd>Expands to "`@'". Example: +``` + %% @doc Contact us at support@@@@@{@hostname}''' + Will generate the text "Contact us at [email protected]" + if the macro `hostname' is bound to + "`vaporware.acme.com'". Also: +``` + %% @doc You might want to write something like + %% @@@@foo that will expand to @@foo and does not start + %% a new tag even if it appears first in a line.''' + </dd> +</dl> + + +== Type specifications == + + === Function specifications === + +The following grammar describes the form of the specifications following +a `@spec' tag. A '`?'' suffix implies that the element is optional. +Function types have higher precedence than union types; e.g., "`(atom()) +-> atom() | integer()'" is parsed as `((atom()) -> atom()) | integer()', +not as `(atom()) -> (atom() | integer())'. + +<table summary="specification syntax grammar"> +<tbody valign="baseline"> + <tr> + <td><code>Spec</code></td> + <td>::=</td> + <td><code>FunType "where"? DefList? + <br/>| FunctionName FunType "where"? DefList?</code></td> + </tr> + <tr> + <td><code>FunctionName</code></td> + <td>::=</td> + <td><code>Atom</code></td> + </tr> + <tr> + <td><code>FunType</code></td> + <td>::=</td> + <td><code>"(" UnionTypes? ")" "->" UnionType</code></td> + </tr> + <tr> + <td><code>UnionTypes</code></td> + <td>::=</td> + <td><code>UnionType + <br/>| UnionType "," UnionTypes</code></td> + </tr> + <tr> + <td><code>UnionType</code></td> + <td>::=</td> + <td><code>UnionList + <br/>| Name "::" UnionList</code></td> + </tr> + <tr> + <td><code>Name</code></td> + <td>::=</td> + <td><code>Variable</code></td> + </tr> + <tr> + <td><code>UnionList</code></td> + <td>::=</td> + <td><code>Type + <br/>| Type "+" UnionList + <br/>| Type "|" UnionList</code></td> + </tr> + <tr> + <td><code>Type</code></td> + <td>::=</td> + <td><code>TypeVariable + <br/>| Atom + <br/>| Integer + <br/>| Float + <br/>| FunType + <br/>| "{" UnionTypes? "}" + <br/>| "[" "]" + <br/>| "[" UnionType "]" + <br/>| "(" UnionType ")" + <br/>| TypeName "(" UnionTypes? ")" + <br/>| ModuleName ":" TypeName "(" UnionTypes? ")" + <br/>| "//" AppName "/" ModuleName ":" TypeName "(" UnionTypes? ")"</code></td> + </tr> + <tr> + <td><code>TypeVariable</code></td> + <td>::=</td> + <td><code>Variable</code></td> + </tr> + <tr> + <td><code>TypeName</code></td> + <td>::=</td> + <td><code>Atom</code></td> + </tr> + <tr> + <td><code>ModuleName</code></td> + <td>::=</td> + <td><code>Atom + <br/>| ModuleName "." Atom</code></td> + </tr> + <tr> + <td><code>AppName</code></td> + <td>::=</td> + <td><code>Atom</code></td> + </tr> + <tr> + <td><code>DefList</code></td> + <td>::=</td> + <td><code>Def + <br/>| DefList Def + <br/>| DefList "," Def</code></td> + </tr> + <tr> + <td><code>Def</code></td> + <td>::=</td> + <td><code>TypeVariable "=" UnionType + <br/>| TypeName "(" TypeVariables? ")" "=" UnionType</code></td> + </tr> + <tr> + <td><code>TypeVariables</code></td> + <td>::=</td> + <td><code>TypeVariable + <br/>| TypeVariable "," TypeVariables</code></td> + </tr> +</tbody> +</table> + + +Examples: +``` + %% @spec my_function(X::integer()) -> integer()''' +``` + %% @spec (X::integer()) -> integer()''' +``` + %% @spec sqrt(float()) -> float()''' +``` + %% @spec pair(S, T) -> {S, T}''' +``` + %% @spec append(List, List) -> List + %% List = [term()]''' +``` + %% @spec append(A::List, B::List) -> List + %% List = [Item] + %% Item = term()''' +``` + %% @spec open(File::filename()) -> FileDescriptor + %% where + %% filename() = string() + atom(), + %% FileDescriptor = term()''' +``` + %% @spec close(graphics:window()) -> ok''' + +In the above examples, `X', `A', `B', +and `File' are parameter names, used for referring to the +parameters from the documentation text. The <em>type variables</em> +`S', `T' and `List' are used to +simplify the type specifications, and may be supplied with +definitions. It is also possible to give definitions for named types, +which means that the name is simply an alias. (Use the +`@type' tag to document abstract data types.) If a named type +is defined in another module, it can be referred to as +`Module:TypeName(...)'. Note that the keyword '`where'' is optional +before a list of definitions, and that the definitions in the list may +optionally be separated by '`,''. + +Both the '`|'' and the '`+'' character may be +used to separate alternatives in union types; there is no semantic +difference. Note that the notation `[Type]' means "proper +(nil-terminated) list whose elements all belong to `Type'"; +For example, `[atom()|integer()]' means the same thing as +`[atom()+integer()]', i.e., a proper list of atoms and/or +integers. + +If only a type variable is given for a parameter, as in +"`pair(S, T) -> ...'", the same variable name may implicitly +be used as the parameter name; there is no need to write +"`pair(S::S, T::T) -> ...'". + +EDoc automatically extracts possible parameter names from the source +code, to be used if no parameter name is given in the specification (or +if the specification is missing altogether). If this fails, EDoc will +generate a dummy parameter name, such as `X1'. This way, EDoc +can often produce helpful documentation even for code that does not +contain any annotations at all. + + === Type definitions === + +The following grammar (see above for auxiliary definitions) describes +the form of the definitions that may follow a `@type' tag: + +<table summary="type definition grammar"> +<tbody valign="baseline"> + <tr> + <td><code>Typedef</code></td> + <td>::=</td> + <td><code>TypeName "(" TypeVariables? ")" DefList? + <br/>| TypeName "(" TypeVariables? ")" "=" UnionType DefList?</code></td> + </tr> +</tbody> +</table> + +(For a truly abstract data type, no equivalence is specified.) The main +definition may be followed by additional local definitions. Examples: +``` + %% @type myList(X). A special kind of lists ...''' +``` + %% @type filename() = string(). Atoms not allowed!''' +``` + %% @type thing(A) = {thong, A} + %% A = term(). + %% A kind of wrapper type thingy.''' + + + === Pre-defined data types === + +The following data types are predefined by EDoc, and may not be +redefined: +``` + any() + atom() + binary() + bool() + char() + cons() + deep_string() + float() + function() + integer() + list() + nil() + none() + number() + pid() + port() + reference() + string() + term() + tuple() +''' +Details: +<ul> + <li>`any()' means "any Erlang data type". + `term()' is simply an alias for `any()'.</li> + <li>`atom()', `binary()', + `float()', `function()', + `integer()', `pid()', `port()' + and `reference()' are primitive data types of + the Erlang programming language.</li> + <li>`bool()' is the subset of `atom()' consisting + of the atoms `true' and `false'.</li> + <li>`char()' is a subset of + `integer()' representing character codes.</li> + <li>`tuple()' is the set of all tuples `{...}'.</li> + <li>`list(T)' is just an alias for `[T]'.</li> + <li>`nil()' is an alias for the empty list `[]'.</li> + <li>`cons(H,T)' is the list constructor. This is usually not + used directly. It is possible to recursively define `list(T) + := nil()+cons(T,list(T))'.</li> + <li>`string()' is an alias for `[char()]'.</li> + <li>`deep_string()' is recursively defined as + `[char()+deep_string()]'.</li> + <li>`none()' means "no data type". E.g., a function + that never returns has type `(...) -> none()'</li> +</ul> + + +== Acknowledgements == + +Since the first version of EDoc, several people have come up with +suggestions (Luke Gorrie, Joe Armstrong, Erik Stenman, Sean Hinde, Ulf +Wiger, ...), and some have even submitted code to demonstrate their +ideas (Vlad Dumitrescu, Johan Blom, Vijay Hirani, ...). None of that +code was actually included in the Great Rewriting that followed the +initial public release (EDoc version 0.1), but most of the central +points were addressed in the new system, such as better modularization +and possibility to plug in different layout engines, and making EDoc +understand the application directory layout. + +It is now getting too hard to keep track of all the people who have made +further suggestions or submitted bug reports, but your input is always +appreciated. Thank you. diff --git a/lib/edoc/doc/pdf/.gitignore b/lib/edoc/doc/pdf/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/edoc/doc/pdf/.gitignore diff --git a/lib/edoc/doc/src/Makefile b/lib/edoc/doc/src/Makefile new file mode 100644 index 0000000000..8d22e1c1da --- /dev/null +++ b/lib/edoc/doc/src/Makefile @@ -0,0 +1,139 @@ +# ``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$ +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk +VSN=$(EDOC_VSN) +APPLICATION=edoc + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) + +# ---------------------------------------------------- +# Man page source directory (with .erl files) +# ---------------------------------------------------- +SRC_DIR = $(ERL_TOP)/lib/edoc/src +INC_DIR = $(ERL_TOP)/lib/edoc/include + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +XML_APPLICATION_FILES = ref_man.xml +XML_REF3_FILES = \ + edoc.xml \ + edoc_doclet.xml \ + edoc_extract.xml \ + edoc_layout.xml \ + edoc_lib.xml \ + edoc_run.xml + +XML_PART_FILES = part.xml part_notes.xml +XML_CHAPTER_FILES = chapter.xml +XML_NOTES_FILES = notes.xml + +BOOK_FILES = book.xml + +XML_FILES=\ + $(BOOK_FILES) $(XML_CHAPTER_FILES) \ + $(XML_PART_FILES) $(XML_REF3_FILES) $(XML_APPLICATION_FILES) \ + $(XML_NOTES_FILES) + +# ---------------------------------------------------- +INFO_FILE = ../../info + +HTML_FILES = \ + $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html) + +EXTRA_FILES = \ + $(DEFAULT_GIF_FILES) \ + $(DEFAULT_HTML_FILES) \ + $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_NOTES_FILES:%.xml=$(HTMLDIR)/%.html) + +MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3) + +HTML_REF_MAN_FILE = $(HTMLDIR)/index.html + +TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf + + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +XML_FLAGS += +DVIPS_FLAGS += + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- +$(HTMLDIR)/%.gif: %.gif + $(INSTALL_DATA) $< $@ + +docs: pdf html man + +$(TOP_PDF_FILE): $(XML_FILES) + +pdf: $(TOP_PDF_FILE) + +html: gifs $(HTML_REF_MAN_FILE) + +man: $(MAN3_FILES) + +$(XML_REF3_FILES): + docb_gen -def vsn $(EDOC_VSN) -includes $(INC_DIR) \ + $(SRC_DIR)/$(@:%.xml=%.erl) + +$(XML_CHAPTER_FILES): + docb_gen -chapter -def vsn $(EDOC_VSN) ../overview.edoc + +gifs: $(GIF_FILES:%=$(HTMLDIR)/%) + +debug opt: + +clean clean_docs: + rm -rf $(HTMLDIR)/* + rm -f $(MAN3DIR)/* + rm -f $(XML_REF3_FILES) $(XML_CHAPTER_FILES) *.html + rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) + rm -f errs core *~ + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_docs_spec: docs + $(INSTALL_DIR) $(RELSYSDIR)/doc/pdf + $(INSTALL_DATA) $(TOP_PDF_FILE) $(RELSYSDIR)/doc/pdf + $(INSTALL_DIR) $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(HTMLDIR)/* \ + $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR) + $(INSTALL_DIR) $(RELEASE_PATH)/man/man3 + $(INSTALL_DATA) $(MAN3DIR)/* $(RELEASE_PATH)/man/man3 + +release_spec: diff --git a/lib/edoc/doc/src/book.xml b/lib/edoc/doc/src/book.xml new file mode 100644 index 0000000000..67b7cdb2d7 --- /dev/null +++ b/lib/edoc/doc/src/book.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE book SYSTEM "book.dtd"> + +<book xmlns:xi="http://www.w3.org/2001/XInclude"> + <header titlestyle="normal"> + <copyright> + <year>2006</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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 online 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. + + </legalnotice> + + <title>EDoc</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + </header> + <insidecover> + </insidecover> + <pagetext>EDoc Application</pagetext> + <preamble> + <contents level="2"></contents> + </preamble> + <parts lift="no"> + <xi:include href="part.xml"/> + </parts> + <applications> + <xi:include href="ref_man.xml"/> + </applications> + <releasenotes> + <xi:include href="notes.xml"/> + </releasenotes> + <listofterms></listofterms> + <index></index> +</book> + diff --git a/lib/edoc/doc/src/fascicules.xml b/lib/edoc/doc/src/fascicules.xml new file mode 100644 index 0000000000..1b9d6bc94d --- /dev/null +++ b/lib/edoc/doc/src/fascicules.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE fascicules SYSTEM "fascicules.dtd"> + +<fascicules> + <fascicule file="part" href="part_frame.html" entry="no"> + User's Guide + </fascicule> + <fascicule file="ref_man" href="ref_man_frame.html" entry="yes"> + Reference Manual + </fascicule> + <fascicule file="part_notes" href="part_notes_frame.html" entry="no"> + Release Notes + </fascicule> +</fascicules> + diff --git a/lib/edoc/doc/src/make.dep b/lib/edoc/doc/src/make.dep new file mode 100644 index 0000000000..b46e36314f --- /dev/null +++ b/lib/edoc/doc/src/make.dep @@ -0,0 +1,21 @@ +# ---------------------------------------------------- +# >>>> Do not edit this file <<<< +# This file was automaticly generated by +# /home/otp/bin/docdepend +# ---------------------------------------------------- + + +# ---------------------------------------------------- +# TeX files that the DVI file depend on +# ---------------------------------------------------- + +book.dvi: book.tex chapter.tex edoc.tex edoc_doclet.tex \ + edoc_extract.tex edoc_layout.tex edoc_lib.tex \ + edoc_run.tex part.tex ref_man.tex + +# ---------------------------------------------------- +# Source inlined when transforming from source to LaTeX +# ---------------------------------------------------- + +book.tex: ref_man.xml + diff --git a/lib/edoc/doc/src/notes.xml b/lib/edoc/doc/src/notes.xml new file mode 100644 index 0000000000..8fcbc8ec70 --- /dev/null +++ b/lib/edoc/doc/src/notes.xml @@ -0,0 +1,221 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2007</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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 online 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. + + </legalnotice> + + <title>EDoc Release Notes</title> + <prepared>otp_appnotes</prepared> + <docno>nil</docno> + <date>nil</date> + <rev>nil</rev> + <file>notes.xml</file> + </header> + <p>This document describes the changes made to the EDoc + application.</p> + +<section><title>Edoc 0.7.6.5</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + The documentation is now built with open source tools + (xsltproc and fop) that exists on most platforms. One + visible change is that the frames are removed.</p> + <p> + Own Id: OTP-8201</p> + </item> + </list> + </section> + +</section> + +<section><title>Edoc 0.7.6.4</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Miscellaneous updates.</p> + <p> + Own Id: OTP-8190</p> + </item> + </list> + </section> + +</section> + +<section><title>Edoc 0.7.6.3</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>The copyright notices have been updated.</p> + <p> + Own Id: OTP-7851</p> + </item> + </list> + </section> + +</section> + +<section><title>Edoc 0.7.6.2</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Minor updates.</p> + <p> + Own Id: OTP-7642</p> + </item> + </list> + </section> + +</section> + +<section><title>Edoc 0.7.6.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Correction to work with new versions of STDLIB that no + longer has the <c>erl_internal:obsolete/3</c> function.</p> + <p> + Own Id: OTP-7539</p> + </item> + </list> + </section> + +</section> + +<section><title>Edoc 0.7.6</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Minor changes.</p> + <p> + Own Id: OTP-7388</p> + </item> + </list> + </section> + +</section> + +<section><title>Edoc 0.7.5</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Minor updates, mostly cosmetic.</p> + <p> + Own Id: OTP-7243</p> + </item> + </list> + </section> + +</section> + + <section> + <title>Edoc 0.7.3</title> + + <section> + <title>Improvements and New Features</title> + <list type="bulleted"> + <item> + <p>Minor Makefile changes.</p> + <p>Own Id: OTP-6689</p> + </item> + <item> + <p>Dialyzer warnings were eliminated.</p> + <p>Own Id: OTP-6737</p> + </item> + </list> + </section> + </section> + + <section> + <title>EDoc 0.7.2</title> + + <section> + <title>Fixed Bugs and Malfunctions</title> + <list type="bulleted"> + <item> + <p>Some missing files have been added: + <c><![CDATA[~/include/edoc_doclet.hrl]]></c>, <c><![CDATA[~/priv/edoc.dtd]]></c>, + <c><![CDATA[~/priv/erlang.png]]></c></p> + <p>Own Id: OTP-6457</p> + </item> + </list> + </section> + + <section> + <title>Improvements and New Features</title> + <list type="bulleted"> + <item> + <list type="bulleted"> + <item>Undefined macros only cause warnings, not errors.</item> + <item>New, built-in <c><![CDATA[@version]]></c> macro.</item> + <item>Documented the <c><![CDATA[@docfile]]></c> and <c><![CDATA[@headerfile]]></c> + generic tags.</item> + <item>Added recognition of <c><![CDATA["TODO:"]]></c> as a wiki + equivalent for <c><![CDATA[@todo]]></c> tags.</item> + <item>Added documentation about overview pages.</item> + <item><c><![CDATA['where']]></c> and <c><![CDATA[',']]></c> are allowed as + separators in specs.</item> + <item>Corrected ambiguity in spec grammar (possible + incompatibility issue - parentheses may need to be added + in some cases, in existing code).</item> + <item>Experimental (and undocumented) support for + <c><![CDATA[@param]]></c> and <c><![CDATA[@return]]></c> tags and corresponding + <c><![CDATA["..."]]></c> annotations on <c><![CDATA[@spec]]></c> parameters.</item> + </list> + <p>*** POTENTIAL INCOMPATIBILITY ***</p> + <p>Own Id: OTP-6568</p> + </item> + </list> + </section> + </section> + + <section> + <title>EDoc 0.7.1</title> + + <section> + <title>Fixed Bugs and Malfunctions</title> + <list type="bulleted"> + <item> + <p>Fixed some broken links in the documentation.</p> + <p>Own Id: OTP-6419</p> + </item> + </list> + </section> + </section> + + <section> + <title>EDoc 0.7.0</title> + <p>Miscellaneous changes.</p> + </section> +</chapter> + diff --git a/lib/edoc/doc/src/part.xml b/lib/edoc/doc/src/part.xml new file mode 100644 index 0000000000..a71b4eda13 --- /dev/null +++ b/lib/edoc/doc/src/part.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE part SYSTEM "part.dtd"> + +<part xmlns:xi="http://www.w3.org/2001/XInclude"> + <header> + <copyright> + <year>2006</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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 online 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. + + </legalnotice> + + <title>EDoc User's Guide</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + </header> + <description> + <p><em>EDoc</em> is the Erlang program documentation generator. + Inspired by the Javadoc (TM) tool for the Java (TM) programming + language, EDoc is adapted to the conventions of the Erlang world, + and has several features not found in Javadoc.</p> + </description> + <xi:include href="chapter.xml"/> +</part> + diff --git a/lib/edoc/doc/src/part_notes.xml b/lib/edoc/doc/src/part_notes.xml new file mode 100644 index 0000000000..42fc39af42 --- /dev/null +++ b/lib/edoc/doc/src/part_notes.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE part SYSTEM "part.dtd"> + +<part xmlns:xi="http://www.w3.org/2001/XInclude"> + <header> + <copyright> + <year>2007</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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 online 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. + + </legalnotice> + + <title>EDoc Release Notes</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + </header> + <description> + <p><em>EDoc</em> is the Erlang program documentation generator. + Inspired by the Javadoc (TM) tool for the Java (TM) programming + language, EDoc is adapted to the conventions of the Erlang world, + and has several features not found in Javadoc.</p> + </description> + <xi:include href="notes.xml"/> +</part> + diff --git a/lib/edoc/doc/src/ref_man.xml b/lib/edoc/doc/src/ref_man.xml new file mode 100644 index 0000000000..619fbaa7ca --- /dev/null +++ b/lib/edoc/doc/src/ref_man.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE application SYSTEM "application.dtd"> + +<application xmlns:xi="http://www.w3.org/2001/XInclude"> + <header> + <copyright> + <year>2006</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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 online 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. + + </legalnotice> + + <title>EDoc Reference Manual</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + </header> + <description> + <p><em>EDoc</em> is the Erlang program documentation generator. + Inspired by the Javadoc (TM) tool for the Java (TM) programming + language, EDoc is adapted to the conventions of the Erlang world, + and has several features not found in Javadoc.</p> + </description> + <xi:include href="edoc.xml"/> + <xi:include href="edoc_doclet.xml"/> + <xi:include href="edoc_extract.xml"/> + <xi:include href="edoc_layout.xml"/> + <xi:include href="edoc_lib.xml"/> + <xi:include href="edoc_run.xml"/> +</application> + diff --git a/lib/edoc/ebin/.gitignore b/lib/edoc/ebin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/edoc/ebin/.gitignore diff --git a/lib/edoc/include/Makefile b/lib/edoc/include/Makefile new file mode 100644 index 0000000000..0533c27567 --- /dev/null +++ b/lib/edoc/include/Makefile @@ -0,0 +1,58 @@ +# ``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$ +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(EDOC_VSN) + +# ---------------------------------------------------- +# Release Macros +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/edoc-$(VSN) + +# ---------------------------------------------------- +# Macros +# ---------------------------------------------------- + +INCLUDE_FILES = edoc_doclet.hrl + +# ---------------------------------------------------- +# Make Rules +# ---------------------------------------------------- +debug opt: + +clean: + +docs: + + +# ---------------------------------------------------- +# Release Targets +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: + $(INSTALL_DIR) $(RELSYSDIR)/include + $(INSTALL_DATA) $(INCLUDE_FILES) $(RELSYSDIR)/include + +release_docs_spec: + diff --git a/lib/edoc/include/edoc_doclet.hrl b/lib/edoc/include/edoc_doclet.hrl new file mode 100644 index 0000000000..a99ff1fbab --- /dev/null +++ b/lib/edoc/include/edoc_doclet.hrl @@ -0,0 +1,63 @@ +%% ===================================================================== +%% Header file for EDoc doclet modules. +%% +%% 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] +%% ===================================================================== + +-define(NO_APP, []). + +%% Context for doclets + +%% @type edoc_context() = #context{dir = string(), +%% env = edoc_lib:edoc_env(), +%% opts = [term()]} + +-record(context, {dir = "", + env, + opts = []}). + +%% Doclet commands + +%% @type no_app(). +%% A value used to mark absence of an Erlang application +%% context. Use the macro `NO_APP' defined in +%% <a href="../include/edoc_doclet.hrl">`edoc_doclet.hrl'</a> +%% to produce this value. + +%% @type doclet_gen() = #doclet_gen{sources = [string()], +%% app = no_app() | atom(), +%% packages = [atom()], +%% modules = [atom()], +%% modules = [atom()], +%% filemap = function()} + +-record(doclet_gen, {sources = [], + app = ?NO_APP, + packages = [], + modules = [], + filemap + }). + +%% @type doclet_toc() = #doclet_gen{paths = [string()], +%% indir = string()} + +-record(doclet_toc, {paths, + indir + }). diff --git a/lib/edoc/info b/lib/edoc/info new file mode 100644 index 0000000000..cb25c1e519 --- /dev/null +++ b/lib/edoc/info @@ -0,0 +1,3 @@ +group: tools +short: A utility used to generate documentation out of tags in source files. + diff --git a/lib/edoc/priv/Makefile b/lib/edoc/priv/Makefile new file mode 100644 index 0000000000..13225e6d1a --- /dev/null +++ b/lib/edoc/priv/Makefile @@ -0,0 +1,54 @@ +# +# 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 +include ../../xmerl/vsn.mk +include ../../syntax_tools/vsn.mk + + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/edoc-$(EDOC_VSN) + + +# +# Common Macros +# + +GEN_SCRIPT_SRC = edoc_generate.src +GEN_SCRIPT = edoc_generate +PRIV_FILES = stylesheet.css erlang.png edoc.dtd + +debug opt: + sed -e "s/%EDOC_VSN%/$(EDOC_VSN)/g" \ + -e "s/%XMERL_VSN%/$(XMERL_VSN)/g" \ + -e "s/%SYNTAX_TOOLS_VSN%/$(SYNTAX_TOOLS_VSN)/g" \ + $(GEN_SCRIPT_SRC) > $(GEN_SCRIPT) + +clean: + rm -f $(GEN_SCRIPT) + rm -f core *~ + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/priv + $(INSTALL_DATA) $(PRIV_FILES) $(RELSYSDIR)/priv + $(INSTALL_SCRIPT) $(GEN_SCRIPT) $(RELSYSDIR)/priv + + +release_docs_spec: + diff --git a/lib/edoc/priv/edoc.dtd b/lib/edoc/priv/edoc.dtd new file mode 100644 index 0000000000..6a332cf22f --- /dev/null +++ b/lib/edoc/priv/edoc.dtd @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- EDoc DTD Version 0.3 --> + +<!ELEMENT overview (title, description?, author*, copyright?, version?, + since?, see*, reference*, todo?, packages, modules)> +<!ATTLIST overview + root CDATA #IMPLIED> + +<!ELEMENT title (#PCDATA)> + +<!ELEMENT package (description?, author*, copyright?, version?, + since?, deprecated?, see*, reference*, todo?, + modules)> +<!ATTLIST package + name CDATA #REQUIRED + root CDATA #IMPLIED> + +<!ELEMENT modules (module+)> + + +<!ELEMENT module (args?, description?, author*, copyright?, version?, + since?, deprecated?, see*, reference*, todo?, + behaviour*, callbacks?, typedecls?, functions)> +<!ATTLIST module + name CDATA #REQUIRED + private (yes | no) #IMPLIED + hidden (yes | no) #IMPLIED + root 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 copyright (#PCDATA)> +<!ELEMENT version (#PCDATA)> +<!ELEMENT since (#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 callback EMPTY> +<!ATTLIST callback + name CDATA #REQUIRED + arity CDATA #REQUIRED> + +<!ELEMENT typedecls (typedecl+)> +<!ELEMENT typedecl (typedef, description?)> +<!ATTLIST typedecl + label CDATA #IMPLIED> + +<!ELEMENT functions (function+)> +<!ELEMENT function (args, typespec?, returns?, throws?, equiv?, + description?, since?, deprecated?, see*, todo?)> +<!ATTLIST function + name CDATA #REQUIRED + arity CDATA #REQUIRED + exported (yes | no) #IMPLIED + label CDATA #IMPLIED> + +<!ELEMENT args (arg*)> +<!ELEMENT arg (argName, description?)> +<!ELEMENT argName (#PCDATA)> + +<!ELEMENT throws (type, localdef*)> +<!ELEMENT returns (description)> + +<!ELEMENT equiv (expr, see?)> +<!ELEMENT expr (#PCDATA)> + +<!ELEMENT erlangName EMPTY> +<!ATTLIST erlangName + app CDATA #IMPLIED + module CDATA #IMPLIED + name CDATA #REQUIRED> + + +<!-- Data type specifications --> + +<!ELEMENT typedef (erlangName, argtypes, type?, localdef*)> + +<!ELEMENT typespec (erlangName, type, localdef*)> + +<!ELEMENT localdef ((typevar | abstype), type)> +<!ATTLIST localdef label CDATA #IMPLIED> + +<!ELEMENT argtypes (type*)> + +<!ELEMENT type (typevar | atom | integer | float | nil | list | tuple | + fun | record | abstype | union)> +<!ATTLIST type name CDATA #IMPLIED> + +<!ELEMENT union (typevar | atom | integer | float | nil | list | tuple | + fun | record | abstype)+> + +<!ELEMENT typevar EMPTY> +<!ATTLIST typevar name CDATA #REQUIRED> + +<!ELEMENT atom EMPTY> +<!ATTLIST atom value CDATA #REQUIRED> + +<!ELEMENT integer EMPTY> +<!ATTLIST integer value CDATA #REQUIRED> + +<!ELEMENT float EMPTY> +<!ATTLIST float value CDATA #REQUIRED> + +<!ELEMENT nil EMPTY> + +<!ELEMENT list (type)> + +<!ELEMENT tuple (type*)> + +<!ELEMENT fun (argtypes, type)> + +<!ELEMENT record (atom, field*)> + +<!ELEMENT field (atom, type)> + +<!ELEMENT abstype (erlangName, type*)> +<!ATTLIST abstype + href CDATA #IMPLIED> diff --git a/lib/edoc/priv/edoc_generate.src b/lib/edoc/priv/edoc_generate.src new file mode 100644 index 0000000000..e87fdbc902 --- /dev/null +++ b/lib/edoc/priv/edoc_generate.src @@ -0,0 +1,76 @@ +#!/bin/sh +# ``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 expressed 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-2000, Ericsson +# Utvecklings AB. All Rights Reserved.'' +# +# $Id$ +# +# + +#EDOC_DIR=/clearcase/otp/internal_tools/edoc +EDOC_DIR=/home/otp/sgml/edoc-%EDOC_VSN% +SYNTAX_TOOLS_DIR=/home/otp/sgml/syntax_tools-%SYNTAX_TOOLS_VSN% +XMERL_DIR=/home/otp/sgml/xmerl-%XMERL_VSN% + +FILE= +APP= +TITLE= +VSN= + +while [ $# -gt 0 ]; do + case $1 in + -file) + FILE=$2; + shift; + shift;; + -app) + APP=$2; + shift; + shift;; + -title) + TITLE=$2; + shift; + shift;; + -vsn) + VSN=$2; + shift; + shift;; + esac +done + +if [ -n "$FILE" ] ; then + EDOC_ARGS="[{layout,otpsgml_layout},{dir,\".\"},{file_suffix,\".sgml\"},{preprocess,true},{includes,[\"$XMERL_DIR/include\"]}]" + erl -boot start_clean -noshell \ + -pa $EDOC_DIR/ebin \ + -pa $SYNTAX_TOOLS_DIR/ebin \ + -pa $XMERL_DIR/ebin \ + -run edoc_run file $FILE $EDOC_ARGS \ + -s erlang halt +elif [ -n "$APP" -a -n "$TITLE" ] ; then + erl -boot start_clean -noshell \ + -pa $EDOC_DIR/ebin \ + -pa $SYNTAX_TOOLS_DIR/ebin \ + -pa $XMERL_DIR/ebin \ + -run edoc_run application $APP "\".\"" "[{title,$TITLE}]" \ + -s erlang halt +elif [ -n "$APP" -a -n "$VSN" ] ; then + erl -boot start_clean -noshell \ + -pa $EDOC_DIR/ebin \ + -pa $SYNTAX_TOOLS_DIR/ebin \ + -pa $XMERL_DIR/ebin \ + -run edoc_run application $APP "\".\"" "[{def,{vsn,\"$VSN\"}}]" \ + -s erlang halt +else + echo "Usage: docb_edoc [-file Filename] | [-app Appname [-title Title |-vsn Vsn]]" +fi diff --git a/lib/edoc/priv/erlang.png b/lib/edoc/priv/erlang.png Binary files differnew file mode 100644 index 0000000000..987a618e24 --- /dev/null +++ b/lib/edoc/priv/erlang.png diff --git a/lib/edoc/priv/stylesheet.css b/lib/edoc/priv/stylesheet.css new file mode 100644 index 0000000000..e426a90483 --- /dev/null +++ b/lib/edoc/priv/stylesheet.css @@ -0,0 +1,55 @@ +/* standard EDoc style sheet */ +body { + font-family: Verdana, Arial, Helvetica, sans-serif; + margin-left: .25in; + margin-right: .2in; + margin-top: 0.2in; + margin-bottom: 0.2in; + color: #000000; + background-color: #ffffff; +} +h1,h2 { + margin-left: -0.2in; +} +div.navbar { + background-color: #add8e6; + padding: 0.2em; +} +h2.indextitle { + padding: 0.4em; + background-color: #add8e6; +} +h3.function,h3.typedecl { + background-color: #add8e6; + padding-left: 1em; +} +div.spec { + margin-left: 2em; + background-color: #eeeeee; +} +a.module,a.package { + text-decoration:none +} +a.module:hover,a.package:hover { + background-color: #eeeeee; +} +ul.definitions { + list-style-type: none; +} +ul.index { + list-style-type: none; + background-color: #eeeeee; +} + +/* + * Minor style tweaks + */ +ul { + list-style-type: square; +} +table { + border-collapse: collapse; +} +td { + padding: 3 +} 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, []). diff --git a/lib/edoc/test/Makefile b/lib/edoc/test/Makefile new file mode 100644 index 0000000000..4ce9799f6d --- /dev/null +++ b/lib/edoc/test/Makefile @@ -0,0 +1,66 @@ +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- + +MODULES= \ + edoc_SUITE + +ERL_FILES= $(MODULES:%=%.erl) + +TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) +INSTALL_PROGS= $(TARGET_FILES) + +EMAKEFILE=Emakefile + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/edoc_test + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- + +ERL_MAKE_FLAGS += +ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/test_server/include + +EBIN = . + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +make_emakefile: + $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \ + > $(EMAKEFILE) + $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) '*_SUITE_make' \ + >> $(EMAKEFILE) + +tests debug opt: make_emakefile + erl $(ERL_MAKE_FLAGS) -make + +clean: + rm -f $(EMAKEFILE) + rm -f $(TARGET_FILES) $(GEN_FILES) + rm -f core + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + +release_tests_spec: make_emakefile + $(INSTALL_DIR) $(RELSYSDIR) + $(INSTALL_DATA) $(EMAKEFILE) $(ERL_FILES) $(RELSYSDIR) + $(INSTALL_DATA) edoc.spec $(RELSYSDIR) + chmod -f -R u+w $(RELSYSDIR) + @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -) + +release_docs_spec: diff --git a/lib/edoc/test/edoc.spec b/lib/edoc/test/edoc.spec new file mode 100644 index 0000000000..8443a28028 --- /dev/null +++ b/lib/edoc/test/edoc.spec @@ -0,0 +1 @@ +{topcase, {dir, "../edoc_test"}}. diff --git a/lib/edoc/test/edoc_SUITE.erl b/lib/edoc/test/edoc_SUITE.erl new file mode 100644 index 0000000000..ea833f89b2 --- /dev/null +++ b/lib/edoc/test/edoc_SUITE.erl @@ -0,0 +1,52 @@ +%% ``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$ +%% +-module(edoc_SUITE). + +-include("test_server.hrl"). + +%% Test server specific exports +-export([all/1]). + +%% Test cases +-export([build_std/1]). + +all(suite) -> + [build_std]. + +build_std(suite) -> + []; +build_std(doc) -> + ["Build some documentation using standard EDoc layout"]; +build_std(Config) when is_list(Config) -> + + ?line DataDir = ?config(data_dir, Config), + ?line Overview1 = filename:join(DataDir, "overview.edoc"), + ?line Overview2 = filename:join(DataDir, "overview.syntax_tools"), + ?line PrivDir = ?config(priv_dir, Config), + + ?line ok = edoc:application(edoc, [{overview, Overview1}, + {def, {vsn,"TEST"}}, + {dir, PrivDir}]), + + ?line ok = edoc:application(syntax_tools, [{overview, Overview2}, + {def, {vsn,"TEST"}}, + {dir, PrivDir}]), + + ?line ok = edoc:application(xmerl, [{dir, PrivDir}]), + + ok. diff --git a/lib/edoc/test/edoc_SUITE_data/overview.edoc b/lib/edoc/test/edoc_SUITE_data/overview.edoc new file mode 100644 index 0000000000..0da5a21759 --- /dev/null +++ b/lib/edoc/test/edoc_SUITE_data/overview.edoc @@ -0,0 +1,910 @@ + -*- html -*- + + EDoc overview page + + +@author Richard Carlsson <[email protected]> +@copyright 2003-2006 Richard Carlsson +@version {@vsn} +@title Welcome to EDoc + +@doc EDoc is the Erlang program documentation generator. Inspired by the +Javadoc<sup><font size="-3">TM</font></sup> tool for the Java<sup><font +size="-3">TM</font></sup> programming language, EDoc is adapted to the +conventions of the Erlang world, and has several features not found in +Javadoc. + +== Contents == + +<ol> + <li>{@section Introduction}</li> + <li>{@section Running EDoc}</li> + <li>{@section Generic tags}</li> + <li>{@section Overview tags}</li> + <li>{@section Module tags}</li> + <li>{@section Function tags}</li> + <li>{@section References}</li> + <li>{@section Notes on XHTML}</li> + <li>{@section Wiki notation}</li> + <li>{@section Macro expansion}</li> + <li>{@section Type specifications}</li> + <li>{@section Acknowledgements}</li> +</ol> + +== Introduction == + +EDoc lets you write the documentation of an Erlang program as +comments in the source code itself, using <em>tags</em> on the form +"`@Name ...'". A source file does not have to contain tags +for EDoc to generate its documentation, but without tags the result will +only contain the basic available information that can be extracted from +the module. + +A tag must be the first thing on a comment line, except for leading +'`%'' characters and whitespace. The comment must be between +program declarations, and not on the same line as any program text. All +the following text - including consecutive comment lines - up until the +end of the comment or the next tagged line, is taken as the +<em>content</em> of the tag. + +Tags are associated with the nearest following program construct "of +significance" (the module name declaration and function +definitions). Other constructs are ignored; e.g., in: +``` + %% @doc Prints the value X. + + -record(foo, {x, y, z}). + + print(X) -> ... +''' +the `@doc' tag is associated with the function `print/1'. + +Note that in a comment such as: +```% % @doc ...''' +the tag is ignored, because only the first '`%'' character is +considered "leading". This allows tags to be "commented out". + +Some tags, such as `@type', do not need to be associated +with any program construct. These may be placed at the end of the file, +in the "footer". + + +== Running EDoc == + +The following are the main functions for running EDoc: + <ul> + <li>{@link edoc:application/2}: Creates documentation for a + typical Erlang application.</li> + <li>{@link edoc:packages/2}: Creates documentation for one or + more packages, automatically locating source files.</li> + <li>{@link edoc:files/2}: Creates documentation for a + specified set of source files.</li> + <li>{@link edoc:run/3}: General interface function; the common + back-end for the above functions. Options are documented here.</li> + </ul> + +Note that the function {@link edoc:file/2} belongs to the old, deprecated +interface (from EDoc version 0.1), and should not be used. + + +== Generic tags == + +The following tags can be used anywhere within a module: +<dl> + <dt><a name="gtag-clear">`@clear'</a></dt> + + <dd>This tag causes all tags above it (up to the previous program + construct), to be discarded, including the `@clear' + tag itself. The text following the tag + is also ignored. <em>This is typically only useful in code + containing conditional compilation, when preprocessing is turned + on.</em> (Preprocessing is turned off by default.) E.g., in +```-ifdef(DEBUG). + %% @doc ... + foo(...) -> ... + -endif. + %% @clear + + %% @doc ... + bar(...) -> ...''' + the `@clear' tag makes sure that EDoc does not see + two `@doc' tags before the function `bar', + even if the code for function `foo' is removed by + preprocessing. (There is no way for EDoc to see what the first + `@doc' tag "really" belongs to, since preprocessing + strips away all such information.)</dd> + + <dt><a name="gtag-end">`@end'</a></dt> + <dd>The text following this tag is always ignored. Use this to + mark the end of the previous tag, when necessary, as e.g. in: +```%% ---------------------------------- + %% ... + %% @doc ... + %% ... + %% @end + %% ----------------------------------''' + to avoid including the last "ruler" line in the + `@doc' tag. + + <em>Note: using some other "dummy" `@'-tag for + the same purpose might work in a particular implementation of + EDoc, but is not guaranteed to. Always use `@end' + to ensure future compatibility.</em></dd> + + <dt><a name="gtag-todo">`@todo' (or `@TODO')</a></dt> + <dd>Attaches a To-Do note to a function, module, package, or + overview-page. The content can be any XHTML text describing + the issue, e.g.: +```%% @TODO Finish writing the documentation.''' + or +```%% @todo Implement <a href="http://www.ietf.org/rfc/rfc2549.txt">RFC 2549</a>.''' + To-Do notes are normally not shown unless the `todo' option is + turned on (see {@link edoc:get_doc/2}).</dd> + + <dt><a name="gtag-type">`@type'</a></dt> + <dd>Documents an abstract data type or type alias. The content + consists of a type declaration or definition, optionally + followed by a period ('`.'') separator and XHTML + text describing the type (i.e., its purpose, use, etc.). There + must be at least one whitespace character between the + '`.'' and the text. See {@section Type specifications} below + for syntax and examples. + All data type descriptions are placed in a separate section of + the documentation, regardless of where the tags occur.</dd> + +</dl> + + + +== Overview tags == + +The following tags can only be used in an overview file: +<dl> + <dt><a name="otag-title">`@title'</a></dt> + <dd>Specifies a title for the overview page. The content + can be arbitrary text.</dd> +</dl> + + +== Module tags == + +The following tags can be used before a module declaration: +<dl> + <dt><a name="mtag-author">`@author'</a></dt> + <dd>Specifies the name of an author, along with contact + information. An e-mail address can be given within `<...>' + delimiters, and a URI within `[...]' delimiters. Both e-mail and + URI are optional, and any surrounding whitespace is stripped from + all strings. + + The name is the first nonempty string that is not within `<...>' + or `[...]', and does not contain only whitespace. (In other words, + the name can come before, between, or after the e-mail and URI, + but cannot be split up; any sections after the first are ignored.) + If an e-mail address is given, but no name, the e-mail string will + be used also for the name. If no `<...>' section is present, but + the name string contains an '`@'' character, it is assumed to be + an e-mail address. Not both name and e-mail may be left out. + + Examples: +```%% @author Richard Carlsson''' + +```%% @author Richard Carlsson <[email protected]> + %% [http://user.it.uu.se/~richardc/]''' + +```%% @author <[email protected]>''' + +```%% @author [email protected] [http://user.it.uu.se/~richardc/]''' + </dd> + +<dt><a name="mtag-copyright">`@copyright'</a></dt> + <dd>Specifies the module copyrights. The content can be + arbitrary text; for example: +``` + %% @copyright 2001-2003 Richard Carlsson''' + </dd> + + <dt><a name="mtag-deprecated">`@deprecated'</a></dt> + <dd>Mark the module as deprecated, indicating that it should no + longer be used. The content must be well-formed XHTML, and should + preferably include a `@{@link}' reference to a + replacement; as in: +``` + %% @deprecated Please use the module @{@link foo} instead.''' + </dd> + + <dt><a name="mtag-doc">`@doc'</a></dt> + <dd>Describes the module, using well-formed XHTML text. The + first sentence is used as a summary (see the + <a href="#ftag-doc">`@doc' function tag</a> below + for details). For example.: +```%% @doc This is a <em>very</em> useful module. It is ...'''</dd> + + <dt><a name="mtag-hidden">`@hidden'</a></dt> + <dd>Marks the module so that it will not appear in the + documentation (even if "private" documentation is generated). + Useful for sample code, test modules, etc. The content can be + used as a comment; it is ignored by EDoc.</dd> + + <dt><a name="mtag-private">`@private'</a></dt> + <dd>Marks the module as private (i.e., not part of the public + interface), so that it will not appear in the normal + documentation. (If "private" documentation is generated, the + module will be included.) The content can be used as a comment; it + is ignored by EDoc.</dd> + + <dt><a name="mtag-reference">`@reference'</a></dt> + <dd>Specifies a reference to some arbitrary external resource, + such as an article, book, or web site. The content must be + well-formed XHTML text. Examples: +```%% @reference Pratchett, T., <em>Interesting Times</em>, + %% Victor Gollancz Ltd, 1994.''' + +```%% @reference See <a href="www.google.com">Google</a> for + %% more information.''' + </dd> + + <dt><a name="mtag-see">`@see'</a></dt> + <dd>See the <a href="#ftag-see">`@see' function tag</a> + below for details.</dd> + + <dt><a name="mtag-since">`@since'</a></dt> + <dd>Specifies when the module was introduced, with respect to + the application, package, release or distribution it is part + of. The content can be arbitrary text.</dd> + + <dt><a name="mtag-version">`@version'</a></dt> + <dd>Specifies the module version. The content can be arbitrary + text.</dd> + +</dl> + + + +== Function tags == + +The following tags can be used before a function definition: +<dl> + <dt><a name="ftag-deprecated">`@deprecated'</a></dt> + <dd>See the <a href="#mtag-deprecated">`@deprecated' + module tag</a> for details.</dd> + + <dt><a name="ftag-doc">`@doc'</a></dt> + <dd>XHTML text describing the function. The first + sentence of the text is used as a quick summary; this ends at + the first period character ('`.'') or exclamation mark + ('`!'') that is followed by a whitespace character, a + line break, or the end of the tag text, and is not within XML + markup. (As an exception, the first sentence may be within an + initial paragraph element)</dd> + + <dt><a name="ftag-equiv">`@equiv'</a></dt> + <dd>Specify equivalence to another function call/expression. + The content must be a proper Erlang expression. If the + expression is a function call, a cross-reference to the called + function is created automatically. Typically, this tag is used + instead of `@doc'. </dd> + + <dt><a name="ftag-hidden">`@hidden'</a></dt> + <dd>Marks the function so that it will not appear in the + documentation (even if "private" documentation is generated). + Useful for debug/test functions, etc. The content can be + used as a comment; it is ignored by EDoc.</dd> + + <dt><a name="ftag-private">`@private'</a></dt> + <dd>Marks the function as private (i.e., not part of the public + interface), so that it will not appear in the normal + documentation. (If "private" documentation is generated, the + function will be included.) Only useful for exported functions, + e.g. entry points for `spawn'. (Non-exported functions are + always "private".) The content can be used as a comment; it is + ignored by EDoc.</dd> + + <dt><a name="ftag-see">`@see'</a></dt> + <dd>Make a reference to a module, function, datatype, or + application. (See {@section References} below.) + The content consists of a reference, optionally followed by a + period ('`.''), one or more whitespace characters, and + XHTML text to be used for the label; for example "`@see edoc'" or + "`@see edoc. <b>EDoc</b>'". If no label text is specified, the + reference itself is used as the label.</dd> + + <dt><a name="ftag-since">`@since'</a></dt> + <dd>Specifies in what version of the module the function was + introduced; cf. the + <a href="#mtag-version">`@version' + module tag</a>. The content can be arbitrary text.</dd> + + <dt><a name="ftag-spec">`@spec'</a></dt> + <dd>Used to specify the function type; see {@section Type + specifications} below for syntax. If the function name is + included in the specification, it must match the name in the + actual code. When parameter names are not given in the + specification, suitable names will be taken from the source + code if possible, and otherwise synthesized.</dd> + + <dt><a name="ftag-throws">`@throws'</a></dt> + <dd>Specifies which types of terms may be thrown by the + function, if its execution terminates abruptly due to a call to + `erlang:throw(Term)'. The content is a type expression (see {@section + Type specifications}), and can be a union type. + + Note that exceptions of type `exit' (as caused by calls to + `erlang:exit(Term)') and `error' (run-time errors such as `badarg' + or `badarith') are not viewed as part of the normal interface of + the function, and cannot be documented with the `@throws' tag.</dd> + + <dt><a name="ftag-type">`@type'</a></dt> + <dd>See the <a href="#gtag-type">`@type' generic tag</a> + for details. Placing a `@type' tag by a function + definition may be convenient, but does not affect where the + description is placed in the generated documentation.</dd> +</dl> + + + +== References == + +In several contexts (`@see' tags, `@link' macros, etc.), EDoc lets +you refer to the generated documentation for modules, functions, +datatypes, and applications, using a simple and compact syntax. The +possible formats for references are: +<table border="1" summary="reference syntax"> + <tr><th>Reference syntax</th><th>Example</th><th>Scope</th></tr> + <tr><td>`Module'</td><td>{@link edoc_run}, `erl.lang.list'</td><td>Global</td></tr> + <tr><td>`Package.*'</td><td>`erl.lang.*'</td><td>Global</td></tr> + <tr><td>`Function/Arity'</td><td>`file/2'</td><td>Within module</td></tr> + <tr><td>`Module:Function/Arity'</td><td>{@link edoc:application/2}</td><td>Global</td></tr> + <tr><td>`Type()'</td><td>`filename()'</td><td>Within module</td></tr> + <tr><td>`Module:Type()'</td><td>{@link edoc:edoc_module()}</td><td>Global</td></tr> + <tr><td>`//Application'</td><td>{@link //edoc}</td><td>Global</td></tr> + <tr><td>`//Application/Module'</td><td>{@link //edoc/edoc_doclet}</td><td>Global</td></tr> + <tr><td>`//Application/Module:Function/Arity'</td><td>{@link //edoc/edoc_run:file/1}</td><td>Global</td></tr> + <tr><td>`//Application/Module:Type()'</td><td>{@link //edoc/edoc:edoc_module()}</td><td>Global</td></tr> +</table> + + +EDoc will resolve references using the information it finds in +`edoc-info'-files at the locations specified with the `doc_path' +option. EDoc will automatically (and somewhat intelligently) try to find +any local `edoc-info'-files using the current code path, and add them to +the end of the `doc_path' list. The target doc-directory is also +searched for an existing info file; this allows documentation to be +built incrementally. (Use the `new' option to ignore any old info +file.) + +Note that if the name of a module, function or datatype is explicitly +qualified with an application (as in "`//edoc/edoc_run'"), this +overrides any other information about that name, and the reference will +be made relative to the location of the application (if it can be +found). This makes it possible to refer to e.g. a module "`fred'" as +"`//foo/fred'" without accidentally getting a reference to +"`//bar/fred'". You should not use this form of explicit references for +names that are local to the application you are currently creating - +they will always be resolved correctly. + +Note that module-local references such as `file/2' only work properly +within a module. In an overview-page like this (i.e., the one you are +currently reading), no module context is available. + +== Notes on XHTML == + +In several places, XHTML markup can be used in the documentation +text, in particular in `@doc' tags. The main differences from +HTML are the following: +<ul> + <li>All elements must have explicit start and end tags, and be + correctly nested. This means that you cannot e.g. write a + `<li>' tag without also writing a corresponding `</li>' + tag in the right place. This could be an annoyance + at times, but has the great advantage that EDoc can report all + malformed XHTML in your source code, rather than propagate the + errors to the generated documentation.</li> + <li>XHTML tag and attribute names should always be lower-case.</li> + <li>Attributes must be quoted, as in e.g. `<a + name="top">'.</li> +</ul> +To write an element like the HTML `<br>', which has no actual content, +you can write either the full `<br></br>', or better, use the XHTML +abbreviated form `<br/>'. + +Since the purpose of EDoc is to document programs, there is also a +limited form of "wiki"-syntax available for making program code easier +to write inline (and to make the doc-comments easier to read). +See {@section Wiki notation} below for details. + +The HTML heading tags `h1' and `h2' are reserved for use by EDoc. +Headings in documentation source code should start at `h3'. There is +however a special syntax for writing headings which avoids using +specific level numbers altogether; see {@section Headings} below for details. + +EDoc uses {@link //xmerl. XMerL} to parse and export XML markup. + + +== Wiki notation == + +When EDoc parses XHTML, it does additional pre- and post-processing of +the text in order to expand certain notation specific to EDoc into +proper XHTML markup. This "wiki" ([http://en.wikipedia.org/wiki/Wiki]) +notation is intended to make it easier to write source code +documentation. + + === Empty lines separate paragraphs === + +Leaving an empty line in XHTML text (i.e., a line which except for +any leading start-of-comment '<tt>%</tt>' characters contains only +whitespace), will make EDoc split the text before and +after the empty line into separate paragraphs. For example: +```%% @doc This will all be part of the first paragraph. + %% It can stretch over several lines and contain <em>any + %% XHTML markup</em>. + %% + %% This is the second paragraph. The above line is + %% regarded as "empty" by EDoc, even though it ends with + %% a space.''' +will generate the following text: +<blockquote><p>This will all be part of the first paragraph. It can +stretch over several lines and contain <em>any XHTML markup</em>.</p> +This is the second paragraph. The above line is regarded as "empty" by +EDoc, even though it ends with a space.</blockquote> + +Paragraph splitting takes place after the actual XHTML parsing. It only +affects block-level text, and not e.g., text within `<pre>' markup, or +text that is already within `<p>' markup. + + === Headings === + +Section headings, sub-headings, and sub-sub-headings, can be written +using the following notation: +```== Heading == + === Sub-heading === + ==== Sub-sub-heading ====''' +Such a heading must be alone on a line, except for whitespace, and +cannot be split over several lines. A link target is automatically +created for the heading, by replacing any whitespace within the text by +a single underscore character. E.g., +```== Concerning Hobbits ==''' +is equivalent to +```<h3><a name="Concerning_Hobbits">Concerning Hobbits</a></h3>''' +Thus, headings using this notation should not contain characters that +may not be part of URL labels, except for whitespace. If you need to +create such headings, you have to use the explicit XHTML markup. + +A hypertext link to a heading written this way can be created using the +`@section' macro, which transforms the argument text into a label as +described above. E.g., +```@{@section Concerning Hobbits}''' +is equivalent to writing +```<a href="#Concerning_Hobbits">Concerning Hobbits</a>''' + +The above expansions take place before XML parsing. + + === External links === + +Writing a URL within brackets, as in "`[http://www.w3c.org/]'", will +generate a hyperlink such as [http://www.w3c.org/], using the URL both +for the destination and the label of the reference, equivalent to writing +"`<a href="http://www.w3c.org/"><tt>http://www.w3c.org/</tt></a>'". This +short-hand keeps external URL references short and readable. The +recognized protocols are `http', `ftp', and `file'. This expansion takes +place before XML parsing. + + === Verbatim quoting === + +In XHTML text, the '<code>`</code>' character (Unicode `000060', +known as "grave accent" or "back-quote") can be used for verbatim +quoting. This expansion takes place before XML parsing. + +<ul> + <li>A character sequence "<code>`...'</code>" or + "<code>``...''</code>" will be expanded to + "`<code>...</code>'", where all occurrences of the special XML + characters '`<'' and '`&'' (and for completeness, also '`>'') in + the quoted text have been escaped to "`<'", "`&'", and + "`>'", respectively. + All whitespace is stripped from the beginning and end of the + quoted text. + + Double back-quotes "<code>``...''</code>" can be used + to quote text containing single '`` ' ''' characters. The automatic + stripping of any surrounding whitespace makes it possible to write + things like "<code>`` 'foo@bar' ''</code>". + + To quote text containing "<code>''</code>" verbatim, + explicit `<code>' markup or similar must be used.</li> + + <li>A character sequence "<code>```...'''</code>" + will be expanded to "`<pre><![CDATA[...]]></pre>'", which disables + all XML markup within the quoted text, and displays the result in + fixed-font with preserved indentation. Whitespace is stripped from + the end of the quoted text, but not from the beginning, except for + whole leading lines of whitespace. This is + useful for multi-line code examples, or displayed + one-liners.</li> + + <li>To produce a single '<code>`</code>'-character in XML + without beginning a new quote, you can write "<code>`'</code>" + (no space between the '<code>`</code>' and the '<code>'</code>'). + You can of course also use the XML character entity + "``'".</li> +</ul> + +Examples: + ```%% @doc ...where the variable `Foo' refers to... ''' + + ```%% @doc ...returns the atom `` '[email protected]' ''... ''' + + <pre> + %% @doc ...use the command ```erl -name foo''' to...</pre> + + <pre> + %% @doc ...as in the following code: + %% ```f(X) -> + %% case X of + %% ... + %% end'''</pre> + + <pre> + %% @doc ...or in the following: + %% ``` + %% g(X) -> + %% fun () -> ... end + %% '''</pre> + + +== Macro expansion == + +Before the content of a tag is parsed, the text undergoes <em>macro +expansion</em>. The syntax for macro calls is: +<pre> + @{@<em>name</em>}</pre> +or +<pre> + @{@<em>name</em> <em>argument</em>}</pre> +where <em>name</em> and <em>argument</em> are separated by one or more +whitespace characters. The argument can be any text, which may contain +other macro calls. The number of non-escaped "<code>@{@</code>" and +"`}'" delimiters must be balanced. + + The argument text is first expanded in the current environment, and +the result is bound to the <em>macro parameter</em>, written +<code>@{@?}</code>. (If no argument is given, <code>@{@?}</code> is +bound to the empty string.) The macro definition is then substituted +for the call, and expansion continues over the resulting text. Recursive +macro expansions are not allowed. + + === User-defined macros === + +Users can define their own macros by using the `def' EDoc +option; see {@link edoc:file/2} and {@link edoc:get_doc/2} for more +information. + + === Predefined macros === + +<dl> + <dt><a name="predefmacro-date"><code>@{@date}</code></a></dt> + <dd>Expands to the current date, as "<tt>Month Day Year</tt>", + e.g. "{@date}".</dd> + + <dt><a name="predefmacro-docRoot"><code>@{@docRoot}</code></a></dt> + <dd>Expands to the relative URL path (such as + `"../../.."') from the current page to the root + directory of the generated documentation. This can be used to + create XHTML references such as `<img + src="@{@docRoot}/images/logo.jpeg">' that are independent of how + deep down in a package structure they occur. If packages are not + used (i.e., if all modules are in the "empty" package), + <code>@{@docRoot}</code> will always resolve to the empty + string.</dd> + + <dt><a name="predefmacro-link"><code>@{@link <em>reference</em>. + <em>description</em>}</code></a></dt> + <dd>This creates a hypertext link; cf. the + <a href="#ftag-see">`@see' function tag</a> above for + details. The description text (including the period separator) + is optional; if no text is given, the reference itself is + used. For example, <code>@{@link edoc:file/2}</code> creates the + link {@link edoc:file/2}, and `@{@link edoc:file/2. <em>this link</em>}' + creates {@link edoc:file/2. <em>this link</em>}.</dd> + + <dt><a name="predefmacro-module"><code>@{@module}</code></a></dt> + <dd>Expands to the name of the current module. Only defined when a + module is being processed.</dd> + + <dt><a name="predefmacro-package"><code>@{@package}</code></a></dt> + <dd>Expands to the name of the current package.</dd> + + <dt><a name="predefmacro-section"><code>@{@section + <em>heading</em>}</code></a></dt> + <dd>Expands to a hypertext link to the specified section heading; + see {@section Headings} for more information.</dd> + + <dt><a name="predefmacro-type"><code>@{@type + <em>type-expression</em>}</code></a></dt> + <dd>Formats a type expression within `<code>...</code>' + markup and with hypertext links for data types. For example, + <code>@{@type {options, List::edoc:option_list()@@}}</code> + generates "{@type {options, List::edoc:option_list()@}}". (Cf. + {@section Escape sequences} below.)</dd> + + <dt><a name="predefmacro-time"><code>@{@time}</code></a></dt> + <dd>Expands to the current time, as "<tt>Hr:Min:Sec</tt>", + e.g. "{@time}".</dd> +</dl> + + === Escape sequences === + +To prevent certain characters from being interpreted as delimiters, +for example to produce the text "<code>@{@</code>" in the output, or use a +'`}'' character in the argument text of a macro call, the +following escape sequences may be used: <dl> + <dt><code>@@{</code></dt> + <dd>Expands to "`{'". Example: +``` + %% @doc A macro call starts with the sequence "@@@{@".''' + </dd> + <dt><code>@@}</code></dt> + <dd>Expands to "`}'". Example: +``` + %% @doc ...@{@foo ...{Key, Value@@}...}...''' + </dd> + <dt><code>@@@@</code></dt> + <dd>Expands to "`@'". Example: +``` + %% @doc Contact us at support@@@@@{@hostname}''' + Will generate the text "Contact us at [email protected]" + if the macro `hostname' is bound to + "`vaporware.acme.com'". Also: +``` + %% @doc You might want to write something like + %% @@@@foo that will expand to @@foo and does not start + %% a new tag even if it appears first in a line.''' + </dd> +</dl> + + +== Type specifications == + + === Function specifications === + +The following grammar describes the form of the specifications +following a `@spec' tag: + +<table summary="specification syntax grammar"> + <tr> + <td><code>Spec</code></td> + <td>::=</td> + <td><code>FunType Def* + <br/>| FunctionName FunType Def*</code></td> + </tr> + <tr> + <td><code>FunctionName</code></td> + <td>::=</td> + <td><code>Atom</code></td> + </tr> + <tr> + <td><code>FunType</code></td> + <td>::=</td> + <td><code>"(" UnionTypes? ")" "->" UnionType</code></td> + </tr> + <tr> + <td><code>UnionTypes</code></td> + <td>::=</td> + <td><code>UnionType + <br/>| UnionType "," UnionTypes</code></td> + </tr> + <tr> + <td><code>UnionType</code></td> + <td>::=</td> + <td><code>UnionList + <br/>| Name "::" UnionList</code></td> + </tr> + <tr> + <td><code>Name</code></td> + <td>::=</td> + <td><code>Variable</code></td> + </tr> + <tr> + <td><code>UnionList</code></td> + <td>::=</td> + <td><code>Type + <br/>| Type "+" UnionList + <br/>| Type "|" UnionList</code></td> + </tr> + <tr> + <td><code>Type</code></td> + <td>::=</td> + <td><code>TypeVariable + <br/>| Atom + <br/>| Integer + <br/>| Float + <br/>| FunType + <br/>| "{" UnionTypes? "}" + <br/>| "[" "]" + <br/>| "[" UnionType "]" + <br/>| TypeName "(" UnionTypes? ")" + <br/>| ModuleName ":" TypeName "(" UnionTypes? ")" + <br/>| "//" AppName "/" ModuleName ":" TypeName "(" UnionTypes? ")"</code></td> + </tr> + <tr> + <td><code>TypeVariable</code></td> + <td>::=</td> + <td><code>Variable</code></td> + </tr> + <tr> + <td><code>TypeName</code></td> + <td>::=</td> + <td><code>Atom</code></td> + </tr> + <tr> + <td><code>ModuleName</code></td> + <td>::=</td> + <td><code>Atom + <br/>| ModuleName "." Atom</code></td> + </tr> + <tr> + <td><code>AppName</code></td> + <td>::=</td> + <td><code>Atom</code></td> + </tr> + <tr> + <td><code>Def</code></td> + <td>::=</td> + <td><code>TypeVariable "=" UnionType + <br/>| TypeName "(" TypeVariables? ")" "=" UnionType</code></td> + </tr> + <tr> + <td><code>TypeVariables</code></td> + <td>::=</td> + <td><code>TypeVariable + <br/>| TypeVariable "," TypeVariables</code></td> + </tr> +</table> + + +Examples: +``` + %% @spec my_function(X::integer()) -> integer()''' +``` + %% @spec (X::integer()) -> integer()''' +``` + %% @spec sqrt(float()) -> float()''' +``` + %% @spec pair(S, T) -> {S, T}''' +``` + %% @spec append(List, List) -> List + %% List = [term()]''' +``` + %% @spec append(A::List, B::List) -> List + %% List = [term()]''' +``` + %% @spec open(File::filename()) -> file_descriptor() + %% filename() = string() + atom()''' +``` + %% @spec close(graphics:window()) -> ok''' + +In the above examples, `X', `A', `B', +and `File' are parameter names, used for referring to the +parameters from the documentation text. The <em>type variables</em> +`S', `T' and `List' are used to +simplify the type specifications, and may be supplied with +definitions. It is also possible to give definitions for named types, +which means that the name is simply an alias. (Use the +`@type' tag to document abstract data types.) If a named type +is defined in another module, it can be referred to as +`Module:TypeName(...)'. + +Both the '`|'' and the '`+'' character may be +used to separate alternatives in union types; there is no semantic +difference. Note that the notation `[Type]' means "proper +(nil-terminated) list whose elements all belong to `Type'"; +For example, `[atom()|integer()]' means the same thing as +`[atom()+integer()]', i.e., a proper list of atoms and/or +integers. + +If only a type variable is given for a parameter, as in +"`pair(S, T) -> ...'", the same variable name may implicitly +be used as the parameter name; there is no need to write +"`pair(S::S, T::T) -> ...'". + +EDoc automatically extracts possible parameter names from the source +code, to be used if no parameter name is given in the specification (or +if the specification is missing altogether). If this fails, EDoc will +generate a dummy parameter name, such as `X1'. This way, EDoc +can often produce helpful documentation even for code that does not +contain any annotations at all. + + === Type definitions === + +The following grammar (see above for auxiliary definitions) describes +the form of the definitions that may follow a `@type' tag: + +<table summary="type definition grammar"> + <tr> + <td><code>Typedef</code></td> + <td>::=</td> + <td><code>TypeName "(" TypeVariables? ")" Def* + <br/>| TypeName "(" TypeVariables? ")" "=" UnionType Def*</code></td> + </tr> +</table> + +(For a truly abstract data type, no equivalence is specified.) The main +definition may be followed by additional local definitions. Examples: +``` + %% @type myList(X). A special kind of lists ...''' +``` + %% @type filename() = string(). Atoms not allowed!''' +``` + %% @type thing(A) = {thong, A} + %% A = term(). + %% A kind of wrapper type thingy.''' + + + === Pre-defined data types === + +The following data types are predefined by EDoc, and may not be +redefined: +``` + any() + atom() + binary() + bool() + char() + cons() + deep_string() + float() + function() + integer() + list() + nil() + none() + number() + pid() + port() + reference() + string() + term() + tuple() +''' +Details: +<ul> + <li>`any()' means "any Erlang data type". + `term()' is simply an alias for `any()'.</li> + <li>`atom()', `binary()', + `float()', `function()', + `integer()', `pid()', `port()' + and `reference()' are primitive data types of + the Erlang programming language.</li> + <li>`bool()' is the subset of `atom()' consisting + of the atoms `true' and `false'.</li> + <li>`char()' is a subset of + `integer()' representing character codes.</li> + <li>`tuple()' is the set of all tuples `{...}'.</li> + <li>`list(T)' is just an alias for `[T]'.</li> + <li>`nil()' is an alias for the empty list `[]'.</li> + <li>`cons(H,T)' is the list constructor. This is usually not + used directly. It is possible to recursively define `list(T) + := nil()+cons(T,list(T))'.</li> + <li>`string()' is an alias for `[char()]'.</li> + <li>`deep_string()' is recursively defined as + `[char()+deep_string()]'.</li> + <li>`none()' means "no data type". E.g., a function + that never returns has type `(...) -> none()'</li> +</ul> + + +== Acknowledgements == + +Since the first version of EDoc, several people have come up with +suggestions (Luke Gorrie, Joe Armstrong, Erik Stenman, Sean Hinde, Ulf +Wiger, ...), and some have even submitted code to demonstrate their +ideas (Vlad Dumitrescu, Johan Blom, Vijay Hirani, ...). None of that +code was actually included in the Great Rewriting that followed the +initial public release (EDoc version 0.1), but most of the central +points were addressed in the new system, such as better modularization +and possibility to plug in different layout engines, and making EDoc +understand the application directory layout. + +It is now getting too hard to keep track of all the people who have made +further suggestions or submitted bug reports, but your input is always +appreciated. Thank you. diff --git a/lib/edoc/test/edoc_SUITE_data/overview.syntax_tools b/lib/edoc/test/edoc_SUITE_data/overview.syntax_tools new file mode 100644 index 0000000000..a63c8b26b2 --- /dev/null +++ b/lib/edoc/test/edoc_SUITE_data/overview.syntax_tools @@ -0,0 +1,76 @@ + +@author Richard Carlsson <[email protected]> +@copyright 1997-2004 Richard Carlsson +@version {@vsn} +@title Erlang Syntax Tools + +@doc This package contains modules for handling abstract Erlang syntax +trees, in a way that is compatible with the "parse trees" of the +standard library module `erl_parse', together with utilities for reading +source files in unusual ways and pretty-printing syntax trees. Also +included is an amazing module merger and renamer called Igor, as well as +an automatic code-cleaner. + +<p>The abstract layer (defined in {@link erl_syntax}) is nicely +structured and the node types are context-independent. The layer makes +it possible to transparently attach source-code comments and user +annotations to nodes of the tree. Using the abstract layer makes +applications less sensitive to changes in the {@link //stdlib/erl_parse} +data structures, only requiring the {@link erl_syntax} module to be +up-to-date.</p> + +<p>The pretty printer {@link erl_prettypr} is implemented on top of the +library module {@link prettypr}: this is a powerful and flexible generic +pretty printing library, which is also distributed separately.</p> + +<p>For a short demonstration of parsing and pretty-printing, simply +compile the included module <a +href="../examples/demo.erl"><code>demo.erl</code></a>, and execute +<code>demo:run()</code> from the Erlang shell. It will compile the +remaining modules and give you further instructions.</p> + +<p>Also try the {@link erl_tidy} module, as follows: +<pre> + erl_tidy:dir("any-erlang-source-dir", [test, old_guard_tests]).</pre> +("<code>test</code>" assures that no files are modified).</p> + +<p>News in 1.4: +<ul> + <li>Added support for {@link erl_syntax:cond_expr/1. cond-expressions}, + {@link erl_syntax:try_expr/4. try-expressions} and + {@link erl_syntax:class_qualifier/2. class-qualifier patterns}.</li> + <li>Added support for parameterized modules.</li> + <li>{@link igor. Igor} is officially included.</li> + <li>Quick-parse functionality added to {@link epp_dodger}.</li> +</ul> +</p> + +<p>News in 1.3: +<ul> + <li>Added support for qualified names (as used by "packages").</li> + <li>Various internal changes.</li> +</ul> +</p> + +<p>News in 1.2: +<ul> + <li>HTML Documentation (generated with EDoc).</li> + <li>A few bug fixes and some minor interface changes (sorry for any + inconvenience).</li> +</ul> +</p> + +<p>News in 1.1: +<ul> + <li>Module {@link erl_tidy}: check or tidy either a single module, or a + whole directory tree recursively. Rewrites and reformats the code + without losing comments or expanding macros. Safe mode allows + generating reports without modifying files.</li> + <li>Module {@link erl_syntax_lib}: contains support functions for easier + analysis of the source code structure.</li> + <li>Module {@link epp_dodger}: Bypasses the Erlang preprocessor - avoids + macro expansion, file inclusion, conditional compilation, etc. + Allows you to find/modify particular definitions/applications of + macros, and other things previously not possible.</li> +</ul> +</p> diff --git a/lib/edoc/vsn.mk b/lib/edoc/vsn.mk new file mode 100644 index 0000000000..7c7ba58cc9 --- /dev/null +++ b/lib/edoc/vsn.mk @@ -0,0 +1 @@ +EDOC_VSN = 0.7.6.5 |