File 5571-ftp-generate-documentation-from-source-code.patch of Package erlang

From ce8323bb96c3a3d267cd9acbb6dd6e8dfa99c3b0 Mon Sep 17 00:00:00 2001
From: Kiko Fernandez-Reyes <kiko@erlang.org>
Date: Fri, 17 Feb 2023 15:09:41 +0100
Subject: [PATCH 1/2] ftp: generate documentation from source code

Generates the types in the documentation of `ftp` from the source code.
Before this change, the documentation was hard-coded in `ftp.xml`.
---
 lib/ftp/doc/specs/.gitignore |   1 +
 lib/ftp/doc/src/Makefile     |   4 +
 lib/ftp/doc/src/ftp.xml      | 736 +++++++++++++----------------------
 lib/ftp/doc/src/specs.xml    |   4 +
 lib/ftp/test/ftp_SUITE.erl   |   2 +-
 5 files changed, 290 insertions(+), 457 deletions(-)
 create mode 100644 lib/ftp/doc/specs/.gitignore
 create mode 100644 lib/ftp/doc/src/specs.xml

diff --git a/lib/ftp/doc/specs/.gitignore b/lib/ftp/doc/specs/.gitignore
new file mode 100644
index 0000000000..322eebcb06
--- /dev/null
+++ b/lib/ftp/doc/specs/.gitignore
@@ -0,0 +1 @@
+specs_*.xml
diff --git a/lib/ftp/doc/src/Makefile b/lib/ftp/doc/src/Makefile
index 38ad4f76d0..6223dcc72b 100644
--- a/lib/ftp/doc/src/Makefile
+++ b/lib/ftp/doc/src/Makefile
@@ -54,6 +54,10 @@ XML_FILES = \
 	$(XML_REF3_FILES)    \
 	$(XML_APPLICATION_FILES)
 
+SPEC_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
+TOP_SPECS_FILE = specs.xml
+
+
 # IMAGE_FILES = ftp.gif
 
 include $(ERL_TOP)/make/doc.mk
diff --git a/lib/ftp/doc/src/ftp.xml b/lib/ftp/doc/src/ftp.xml
index a70369a777..7ea8023b24 100644
--- a/lib/ftp/doc/src/ftp.xml
+++ b/lib/ftp/doc/src/ftp.xml
@@ -81,203 +81,23 @@
     <p>The FTP client can be started and stopped dynamically in runtime by
       calling the <c>ftp</c> application API
       <c>ftp:open(Host, Options)</c> and
-      <c>ftp:close(Client)</c>.</p>
-
-    <p>The available configuration options are as follows:</p>
-    
-    <taglist>
-      <tag>{host, Host}</tag>
-      <item>
-        <marker id="host"></marker>
-	<p>Host = <c>string() | ip_address()</c></p>
-      </item>
-
-      <tag>{port, Port}</tag>
-      <item>
-        <marker id="port"></marker>
-	<p>Port = <c>integer() > 0</c></p>
-	<p>Default is <c>0</c> which aliases to <c>21</c> or <c>990</c> when used with
-	   <seeerl marker="#open"><c>{tls_sec_method,ftps}</c></seeerl>).</p>
-      </item>
-
-      <tag>{mode, Mode}</tag>
-      <item>
-        <marker id="mode"></marker>
-	<p>Mode = <c>active | passive</c></p>
-	<p>Default is <c>passive</c>.</p> 
-      </item>
-
-      <tag>{verbose, Verbose}</tag>
-      <item>
-        <marker id="verbose"></marker>
-	<p>Verbose = <c>boolean()</c> </p>
-	<p>Determines if the FTP communication is to be
-	  verbose or not.</p>
-	<p>Default is <c>false</c>.</p>
-      </item>
-
-      <tag>{debug, Debug}</tag>
-      <item>
-        <marker id="debug"></marker>
-	<p>Debug = <c>trace | debug | disable</c> </p>
-	<p>Debugging using the dbg toolkit. </p>
-	<p>Default is <c>disable</c>.</p>
-      </item>
-
-      <tag>{ipfamily, IpFamily}</tag>
-      <item>
-        <marker id="ipfamily"></marker>
-	<p>IpFamily = <c>inet | inet6 | inet6fb4</c> </p>
-	<p>With <c>inet6fb4</c> the client behaves as before, that is, 
-	  tries to use IPv6, and only if that does not work it
-          uses IPv4).</p>
-	<p>Default is <c>inet</c> (IPv4).</p>
-      </item>
-
-      <tag>{timeout, Timeout}</tag>
-      <item>
-        <marker id="timeout"></marker>
-	<p>Timeout = <c>non_neg_integer()</c></p>
-	<p>Connection time-out.</p>
-	<p>Default is <c>60000</c> (milliseconds).</p>
-      </item>
-
-      <tag>{dtimeout, DTimeout}</tag>
-      <item>
-        <marker id="dtimeout"></marker>
-	<p>DTimeout = <c>non_neg_integer() | infinity</c> </p>
-	<p>Data connect time-out. 
-	The time the client waits for the server to connect to the 
-	data socket.</p>
-	<p>Default is <c>infinity</c>. </p>
-      </item>
-
-      <tag>{progress, Progress}</tag>
-      <item>
-        <marker id="progress"></marker>
-	<p>Progress = <c>ignore | {CBModule, CBFunction, InitProgress}</c></p>
-	<p><c>CBModule = atom()</c>, <c>CBFunction = atom()</c></p>
-	<p><c>InitProgress = term()</c></p>
-	<p>Default is <c>ignore</c>.</p>
-      </item>  
-
-    </taglist>
-  
-    <p>Option <c>progress</c> is intended to be used by applications that
-      want to create some type of progress report, such as a progress bar in
-      a GUI. Default for the progress option is <c>ignore</c>,
-      that is, the option is not used. When the progress option is
-      specified, the following happens when <c>ftp:send/[3,4]</c> or
-      <c>ftp:recv/[3,4]</c> are called:</p>
-    
-    <list type="bulleted">
-      <item>
-	<p>Before a file is transferred, the following call is
-	  made to indicate the start of the file transfer and how large
-	  the file is. The return value of the callback function
-	  is to be a new value for the <c>UserProgressTerm</c> that will
-	  be used as input the next time the callback function is
-	  called.</p>
-	<p><c>
-	  CBModule:CBFunction(InitProgress, File, {file_size, FileSize})
-	</c></p>
-      </item>
-      
-      <item>
-	<p>Every time a chunk of bytes is transferred the
-	  following call is made:</p>
-	<p><c>
-	    CBModule:CBFunction(UserProgressTerm, File, {transfer_size, TransferSize})
-        </c></p>
-      </item>
-
-      <item>
-	<p>At the end of the file the following call is
-	  made to indicate the end of the transfer:</p>
-	<p><c>
-	    CBModule:CBFunction(UserProgressTerm, File, {transfer_size, 0})      
-	</c></p>
-      </item>
-    </list>
-    
-    <p>The callback function is to be defined as follows:</p>
-
-    <p><c>
-	CBModule:CBFunction(UserProgressTerm, File, Size) -> UserProgressTerm
-    </c></p>
-    
-    <p><c>
-	CBModule = CBFunction = atom()
-      </c></p>
-
-    <p><c>
-	UserProgressTerm = term()
-      </c></p>
-
-    <p><c>
-	File = string()
-      </c></p>
-
-    <p><c>
-	Size = {transfer_size, integer()} | {file_size, integer()} | {file_size, unknown}
-    </c></p>
-    
-    <p>For remote files, <c>ftp</c> cannot determine the
-      file size in a platform independent way. In this case the size
-      becomes <c>unknown</c> and it is left to the application to
-      determine the size.</p>
-    
-    <note>
-      <p>The callback is made by a middleman process, hence the
-	file transfer is not affected by the code in the progress
-	callback function. If the callback crashes, this is
-	detected by the FTP connection process, which then prints an
-	info-report and goes on as if the progress option was set
-	to <c>ignore</c>.</p>
-    </note>
-    
-    <p>The file transfer type is set to the default of the FTP server
-      when the session is opened. This is usually ASCII mode.
-    </p>
-    
-    <p>The current local working directory (compare <c>lpwd/1</c>) is set 
-      to the value reported by <c>file:get_cwd/1</c>, the wanted
-      local directory.
-    </p>
-    
-    <p>The return value <c>Pid</c> is used as a reference to the
-      newly created FTP client in all other functions, and they are to
-      be called by the process that created the connection. The FTP
-      client process monitors the process that created it and
-      terminates if that process terminates.</p>   
+      <c>ftp:close(Client)</c>. </p>
   </section>
   
   <section>
-    <title>DATA TYPES</title>
+    <title>Data Types</title>
     <p>The following type definitions are used by more than one
       function in the FTP client API:</p>
     <p><c>pid()</c> = identifier of an FTP connection</p>
     <p><c>string()</c> = list of ASCII characters</p>
-    <p><c>shortage_reason()</c> = <c>etnospc | epnospc</c></p>
-    <p><c>restriction_reason()</c> = <c>epath | efnamena | elogin | enotbinary</c>
-      -  all restrictions are not always relevant to all functions
-    </p>
-    <p><c>common_reason()</c> = <c>econn | eclosed | term()</c> 
-      - some explanation of what went wrong</p>
-
     <marker id="account"></marker>
   </section>
 
   <funcs>
 
     <func>
-      <name since="">account(Pid, Account) -> ok | {error, Reason}</name>
+      <name since="" name="account" arity="2" />
       <fsummary>Specifies which account to use.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Account = string()</v>
-        <v>Reason = eacct | common_reason()</v>
-      </type>
       <desc>
         <p>Sets the account for an operation, if needed.</p>
 
@@ -288,15 +108,10 @@
     </func>
 
     <func>
-      <name since="">append(Pid, LocalFile) -> </name>
-      <name since="">append(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name>
-      <fsummary>Transfers a file to remote server, and appends it to 
+      <name since="" name="append" arity="2"/>
+      <name since="" name="append" arity="3"/>
+      <fsummary>Transfers a file to remote server, and appends it to
       <c>Remotefile</c>.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>LocalFile = RemoteFile = string()</v>
-        <v>Reason = epath | elogin | etnospc | epnospc | efnamena | common_reason</v>
-      </type>
       <desc>
         <p>Transfers the file <c>LocalFile</c> to the remote server. If 
           <c>RemoteFile</c> is specified, the name of the remote file that the
@@ -309,17 +124,11 @@
     </func>
 
     <func>
-      <name since="">append_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
+      <name since="" name="append_bin" arity="3"/>
       <fsummary>Transfers a binary into a remote file.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Bin = binary()</v>
-        <v>RemoteFile = string()</v>
-        <v>Reason = restriction_reason()| shortage_reason() | common_reason()</v>
-      </type>
       <desc>
         <p>Transfers the binary <c>Bin</c> to the remote server and appends 
-          it to the file <c>RemoteFile</c>. If the file does not exist, it 
+          it to the file <c><anno>RemoteFile</anno></c>. If the file does not exist, it
           is created.</p>
 
        <marker id="append_chunk"></marker>
@@ -327,15 +136,10 @@
     </func>
 
     <func>
-      <name since="">append_chunk(Pid, Bin) -> ok | {error, Reason}</name>
+      <name since="" name="append_chunk" arity="2" />
       <fsummary>Appends a chunk to the remote file.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Bin = binary()</v>
-        <v>Reason = echunk | restriction_reason() | common_reason()</v>
-      </type>
       <desc>
-        <p>Transfers the chunk <c>Bin</c> to the remote server, which 
+        <p>Transfers the chunk <c><anno>Bin</anno></c> to the remote server, which
           appends it to the file specified in the call to 
           <c>append_chunk_start/2</c>.</p>
         <p>For some errors, for example, file system full, it is 
@@ -347,16 +151,11 @@
     </func>
 
     <func>
-      <name since="">append_chunk_start(Pid, File) -> ok | {error, Reason}</name>
+      <name since="" name="append_chunk_start" arity="2" />
       <fsummary>Starts transfer of file chunks for appending to <c>File</c>.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>File = string()</v>
-        <v>Reason = restriction_reason() | common_reason()</v>
-      </type>
       <desc>
         <p>Starts the transfer of chunks for appending to the file 
-          <c>File</c> at the remote server. If the file does not exist, 
+          <c><anno>RemoteFile</anno></c> at the remote server. If the file does not exist,
           it is created.</p>
 
        <marker id="append_chunk_end"></marker>
@@ -364,12 +163,8 @@
     </func>
 
     <func>
-      <name since="">append_chunk_end(Pid) -> ok | {error, Reason}</name>
+      <name since="" name="append_chunk_end" arity="1"/>
       <fsummary>Stops transfer of chunks for appending.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Reason = echunk | restriction_reason() | shortage_reason() </v>
-      </type>
       <desc>
         <p>Stops transfer of chunks for appending to the remote server. 
           The file at the remote server, specified in the call to 
@@ -380,27 +175,19 @@
     </func>
 
     <func>
-      <name since="">cd(Pid, Dir) -> ok | {error, Reason}</name>
+      <name since="" name="cd" arity="2" />
       <fsummary>Changes remote working directory.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Dir = string()</v>
-        <v>Reason = restriction_reason() | common_reason() </v>
-      </type>
       <desc>
         <p>Changes the working directory at the remote server to 
-          <c>Dir</c>.</p>
+          <c><anno>Dir</anno></c>.</p>
 
        <marker id="close"></marker>
       </desc>
     </func>
     
     <func>
-      <name since="">close(Pid) -> ok</name>
+      <name since="" name="close" arity="1" />
       <fsummary>Ends the FTP session.</fsummary>
-      <type>
-	<v>Pid = pid()</v>
-      </type>
       <desc>
 	<p>Ends an FTP session, created using function
           <seeerl marker="#open">open</seeerl>.</p>
@@ -410,13 +197,8 @@
     </func>
 
     <func>
-      <name since="">delete(Pid, File) -> ok | {error, Reason}</name>
+      <name since="" name="delete" arity="2"/>
       <fsummary>Deletes a file at the remote server.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>File = string()</v>
-        <v>Reason = restriction_reason() | common_reason()</v>
-      </type>
       <desc>
         <p>Deletes the file <c>File</c> at the remote server.</p>
 
@@ -425,11 +207,8 @@
     </func>
 
     <func>
-      <name since="">formaterror(Tag) -> string()</name>
+      <name since="" name="formaterror" arity="1"/>
       <fsummary>Returns error diagnostics.</fsummary>
-      <type>
-        <v>Tag = {error, atom()} | atom()</v>
-      </type>
       <desc>
         <p>Given an error return value <c>{error, AtomReason}</c>,
           this function returns a readable string describing the error.</p>
@@ -439,26 +218,18 @@
     </func>
 
     <func>
-      <name since="">lcd(Pid, Dir) -> ok | {error, Reason}</name>
+      <name since="" name="lcd" arity="2" />
       <fsummary>Changes local working directory.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Dir = string()</v>
-        <v>Reason = restriction_reason()</v>
-      </type>
       <desc>
-        <p>Changes the working directory to <c>Dir</c> for the local client.</p>
+        <p>Changes the working directory to <c><anno>Dir</anno></c> for the local client.</p>
 
        <marker id="lpwd"></marker>
       </desc>
     </func>
 
     <func>
-      <name since="">lpwd(Pid) -> {ok, Dir}</name>
+      <name since="" name="lpwd" arity="1" />
       <fsummary>Gets local current working directory.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-      </type>
       <desc>
         <p>Returns the current working directory at the local client.</p>
 
@@ -469,21 +240,15 @@
     </func>
 
     <func>
-      <name since="">ls(Pid) -> </name>
-      <name since="">ls(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
+      <name since="" name="ls" arity="1" />
+      <name since="" name="ls" arity="2" />
       <fsummary>List of files.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Pathname = string()</v>
-        <v>Listing = string()</v>
-        <v>Reason = restriction_reason() | common_reason()</v>
-      </type>
       <desc>
         <p>Returns a list of files in long format.</p>
-        <p><c>Pathname</c> can be a directory, a group of files, or 
-           a file. The <c>Pathname</c> string can contain wildcards.</p>
+        <p><c><anno>Dir</anno></c> can be a directory or
+           a file. The <c><anno>Dir</anno></c> string can contain wildcards.</p>
         <p><c>ls/1</c> implies the current remote directory of the user.</p>
-        <p>The format of <c>Listing</c> depends on the operating system. 
+        <p>The format of <c><anno>Listing</anno></c> depends on the operating system.
           On UNIX, it is typically produced from the output of the 
           <c>ls -l</c> shell command.</p>
 
@@ -492,15 +257,10 @@
     </func>
 
     <func>
-      <name since="">mkdir(Pid, Dir) -> ok | {error, Reason}</name>
+      <name since="" name="mkdir" arity="2" />
       <fsummary>Creates a remote directory.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Dir = string()</v>
-        <v>Reason = restriction_reason() | common_reason()</v>
-      </type>
       <desc>
-        <p>Creates the directory <c>Dir</c> at the remote server.</p>
+        <p>Creates the directory <c><anno>Dir</anno></c> at the remote server.</p>
 
        <marker id="nlist"></marker>
        <marker id="nlist1"></marker>
@@ -509,21 +269,15 @@
     </func>
 
     <func>
-      <name since="">nlist(Pid) -> </name>
-      <name since="">nlist(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
+      <name since="" name="nlist" arity="1" />
+      <name since="" name="nlist" arity="2" />
       <fsummary>List of files.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Pathname = string()</v>
-        <v>Listing = string()</v>
-        <v>Reason = restriction_reason() | common_reason()</v>
-      </type>
       <desc>
         <p>Returns a list of files in short format.</p>
-        <p><c>Pathname</c> can be a directory, a group of files, or 
-           a file. The <c>Pathname</c> string can contain wildcards.</p>
+        <p><c><anno>Pathname</anno></c> can be a directory or
+           a file. The <c><anno>Pathname</anno></c> string can contain wildcards.</p>
         <p><c>nlist/1</c> implies the current remote directory of the user.</p>
-        <p>The format of <c>Listing</c> is a stream of
+        <p>The format of <c><anno>Listing</anno></c> is a stream of
           filenames where each filename is separated by &lt;CRLF&gt; or
           &lt;NL&gt;. Contrary to function <c>ls</c>, the purpose of
           <c>nlist</c> is to enable a program to
@@ -534,76 +288,229 @@
     </func>
 
     <func>
-      <name since="">open(Host) -> {ok, Pid} | {error, Reason}</name>
-      <name since="">open(Host, Opts) -> {ok, Pid} | {error, Reason}</name>
+      <name since="" name="open" arity="1" />
+      <name since="" name="open" arity="2" />
       <fsummary>Starts a FTP client.</fsummary>
-      <type>
-	<v>Host = string() | ip_address()</v>
-	<v>Opts = options()</v>
-	<v>options() = [option()]</v>
-	<v>option() = start_option() | open_option()</v>
-	<v>start_option() = {verbose, verbose()} | {debug, debug()}</v>
-	<v>verbose() = boolean() (default is false)</v>
-	<v>debug() = disable | debug | trace (default is disable)</v>
-	<v>open_option() = {ipfamily, ipfamily()} | {port, port()} | {mode, mode()} | {tls, tls_options()} | {tls_sec_method, tls_sec_method()} | {tls_ctrl_session_reuse, boolean() (default is false)} | {timeout, timeout()} | {dtimeout, dtimeout()} | {progress, progress()} | {sock_ctrl, sock_opts()} | {sock_data_act, sock_opts()} | {sock_data_pass, sock_opts()}</v>
-	<v>ipfamily() = inet | inet6 | inet6fb4 (default is inet)</v>
-	<v>port() = non_neg_integer() (default is 0 which aliases to 21 or 990 when used with {tls_sec_method,ftps})</v>
-	<v>mode() = active | passive (default is passive)</v>
-	<v>tls_options() = [<seetype marker="ssl:ssl#tls_option">ssl:tls_option()</seetype>]</v>
-	<v>tls_sec_method() = ftps | ftpes (default is ftpes)</v>
-	<v>sock_opts() = [<seetype marker="kernel:gen_tcp#option">gen_tcp:option()</seetype> except for ipv6_v6only, active, packet, mode, packet_size and header</v>
-	<v>timeout() = integer() > 0 (default is 60000 milliseconds)</v>
-	<v>dtimeout() = integer() > 0 | infinity (default is infinity)</v>
-	<v>progress() = ignore | {module(), function(), initial_data()} (default is ignore)</v>
-	<v>module() = atom()</v>
-	<v>function() = atom()</v>
-	<v>initial_data() = term()</v>
-	<v>Reason = ehost | term()</v>
-      </type>
-
       <desc>
 	<p>Starts a FTP client process and
           opens a session with the FTP server at <c>Host</c>. </p>
+  <p>A session opened in this way is closed using function
+          <seeerl marker="#close">close</seeerl>.</p>
+
+    <p>The available configuration options are as follows:</p>
 
-	<p>If option <c>{tls, tls_options()}</c> is present, the FTP session 
-	is transported over <c>tls</c> (<c>ftps</c>, see 
-	<url href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</url>). 
-	The list <c>tls_options()</c> can be empty. The function 
-	<seemfa marker="ssl:ssl#connect/3"><c>ssl:connect/3</c></seemfa> 
+    <taglist>
+      <tag>{host, Host}</tag>
+      <item>
+        <marker id="host"></marker>
+	<p>Host = <c>string() | ip_address()</c></p>
+      </item>
+
+      <tag>{port, Port}</tag>
+      <item>
+        <marker id="port"></marker>
+	<p>Default is <c>0</c> which aliases to <c>21</c> or <c>990</c> when used with
+	   <seeerl marker="#open"><c>{tls_sec_method,ftps}</c></seeerl>).</p>
+      </item>
+
+      <tag>{mode, Mode}</tag>
+      <item>
+        <marker id="mode"></marker>
+	<p>Default is <c>passive</c>.</p>
+      </item>
+
+      <tag>{verbose, Verbose}</tag>
+      <item>
+        <marker id="verbose"></marker>
+	<p>Determines if the FTP communication is to be
+	  verbose or not.</p>
+	<p>Default is <c>false</c>.</p>
+      </item>
+
+      <tag>{debug, Debug}</tag>
+      <item>
+        <marker id="debug"></marker>
+	<p>Debugging using the dbg toolkit. </p>
+	<p>Default is <c>disable</c>.</p>
+      </item>
+
+      <tag>{ipfamily, IpFamily}</tag>
+      <item>
+        <marker id="ipfamily"></marker>
+	<p>With <c>inet6fb4</c> the client behaves as before, that is,
+	  tries to use IPv6, and only if that does not work it
+          uses IPv4).</p>
+	<p>Default is <c>inet</c> (IPv4).</p>
+      </item>
+
+      <tag>{timeout, Timeout}</tag>
+      <item>
+        <marker id="timeout"></marker>
+	<p>Connection time-out.</p>
+	<p>Default is <c>60000</c> (milliseconds).</p>
+      </item>
+
+      <tag>{dtimeout, DTimeout}</tag>
+      <item>
+        <marker id="dtimeout"></marker>
+	<p>Data connect time-out.
+	The time the client waits for the server to connect to the
+	data socket.</p>
+	<p>Default is <c>infinity</c>. </p>
+      </item>
+
+      <tag>{tls, TLSOptions}</tag>
+	<item><marker id="tls_options"></marker>
+    <p>The FTP session is transported over <c>tls</c> (<c>ftps</c>, see
+	<url href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</url>).
+	The list <c><anno>TLSOptions</anno></c> can be empty. The function
+	<seemfa marker="ssl:ssl#connect/3"><c>ssl:connect/3</c></seemfa>
 	is used for securing both the control connection and the data sessions.
 	</p>
+  </item>
 
-	<p>The suboption <c>{tls_sec_method, tls_sec_method()}</c> (defaults to
-	<c>ftpes</c>) when set to <c>ftps</c> will connect immediately with SSL
+  	<tag>{tls_sec_method, TLSSecMethod}</tag>
+    <item><marker id="tls_sec_method"></marker>
+      <p>When set to <c>ftps</c> will connect immediately with SSL
 	instead of upgrading with STARTTLS.  This suboption is ignored unless
-	the suboption <c>tls</c> is also set.
-	</p>
+	the suboption <c>tls</c> is also set.</p>
+  <p>Default is <c>ftpes</c></p>
+  </item>
 
-	<p>The option <c>{tls_ctrl_session_reuse, boolean()}</c> (defaults to
-	<c>false</c>) when set to <c>true</c> the client will re-use the
+	<tag>{tls_ctrl_session_reuse, boolean()}</tag>
+  <item><marker id="tls_ctrl_session_reuse"></marker>
+    <p>When set to <c>true</c> the client will re-use the
 	TLS session from the control channel on the data channel as enforced by
 	many FTP servers as (<url href="https://scarybeastsecurity.blogspot.com/2009/02/vsftpd-210-released.html">proposed and implemented first by vsftpd</url>).
 	</p>
+  <p>Default is <c>false</c>.</p>
+  </item>
+
+  <tag>{sock_ctrl, SocketCtrls :: [SocketControl :: gen_tcp:option()]}</tag>
+  <item>
+    <p>Passes options from <c>SocketCtrls</c> down to the underlying transport layer (tcp).</p>
+    <p><seetype marker="kernel:gen_tcp#option">gen_tcp:option()</seetype> except for <c>ipv6_v6only</c>,
+      <c>active</c>, <c>packet</c>, <c>mode</c>, <c>packet_size</c> and <c>header</c>.</p>
+    <p>Default value is <c>SocketCtrls = []</c>.</p>
+  </item>
+
+  <tag>{sock_data_act, [SocketControl]}</tag>
+  <item>
+    <p>Passes options from <c>[SocketControl]</c> down to the underlying transport layer (tcp).</p>
+    <p><c>sock_data_act</c> uses the value of <c>sock_ctrl</c> as default value.</p>
+  </item>
+
+  <tag>{sock_data_pass, [SocketControl]}</tag>
+  <item>
+    <p>Passes options from <c>[SocketControl]</c> down to the underlying transport layer (tcp).
+    </p>
+    <p><c>sock_data_pass</c> uses the value of <c>sock_ctrl</c> as default value.</p>
+  </item>
 
-	<p>The options <c>sock_ctrl</c>, <c>sock_data_act</c> and <c>sock_data_pass</c> passes options down to 
-	the underlying transport layer (tcp). The default value for <c>sock_ctrl</c> is <c>[]</c>. Both 
-	<c>sock_data_act</c> and <c>sock_data_pass</c> uses the value of <c>sock_ctrl</c> as default value.
-	</p>
+      <tag>{progress, Progress}</tag>
+      <item>
+        <marker id="progress"></marker>
+	<p>Progress = <c>ignore | {Module, Function, InitialData}</c></p>
+	<p><c>Module = atom()</c>, <c>Function = atom()</c></p>
+	<p><c>InitialData = term()</c></p>
+	<p>Default is <c>ignore</c>.</p>
+
+  <p>Option <c>progress</c> is intended to be used by applications that
+      want to create some type of progress report, such as a progress bar in
+      a GUI. Default for the progress option is <c>ignore</c>,
+      that is, the option is not used. When the progress option is
+      specified, the following happens when <c>ftp:send/[3,4]</c> or
+      <c>ftp:recv/[3,4]</c> are called:</p>
+
+    <list type="bulleted">
+      <item>
+	<p>Before a file is transferred, the following call is
+	  made to indicate the start of the file transfer and how large
+	  the file is. The return value of the callback function
+	  is to be a new value for the <c>UserProgressTerm</c> that will
+	  be used as input the next time the callback function is
+	  called.</p>
+	<p><c>
+	  Module:Function(InitialData, File, {file_size, FileSize})
+	</c></p>
+      </item>
+
+      <item>
+	<p>Every time a chunk of bytes is transferred the
+	  following call is made:</p>
+	<p><c>
+	    Module:Function(UserProgressTerm, File, {transfer_size, TransferSize})
+        </c></p>
+      </item>
+
+      <item>
+	<p>At the end of the file the following call is
+	  made to indicate the end of the transfer:</p>
+	<p><c>
+	    Module:Function(UserProgressTerm, File, {transfer_size, 0})
+	</c></p>
+      </item>
+    </list>
+
+    <p>The callback function is to be defined as follows:</p>
+
+    <p><c>
+	Module:Function(UserProgressTerm, File, Size) -> UserProgressTerm
+    </c></p>
+
+    <p><c>
+	UserProgressTerm = term()
+      </c></p>
+
+    <p><c>
+	File = string()
+      </c></p>
+
+    <p><c>
+	Size = {transfer_size, integer()} | {file_size, integer()} | {file_size, unknown}
+    </c></p>
+
+    <p>For remote files, <c>ftp</c> cannot determine the
+      file size in a platform independent way. In this case the size
+      becomes <c>unknown</c> and it is left to the application to
+      determine the size.</p>
+
+    <note>
+      <p>The callback is made by a middleman process, hence the
+	file transfer is not affected by the code in the progress
+	callback function. If the callback crashes, this is
+	detected by the FTP connection process, which then prints an
+	info-report and goes on as if the progress option was set
+	to <c>ignore</c>.</p>
+    </note>
+
+    <p>The file transfer type is set to the default of the FTP server
+      when the session is opened. This is usually ASCII mode.
+    </p>
+
+    <p>The current local working directory (compare <c>lpwd/1</c>) is set
+      to the value reported by <c>file:get_cwd/1</c>, the wanted
+      local directory.
+    </p>
+
+    <p>The return value <c>Pid</c> is used as a reference to the
+      newly created FTP client in all other functions, and they are to
+      be called by the process that created the connection. The FTP
+      client process monitors the process that created it and
+      terminates if that process terminates.</p>
+
+      </item>
+
+    </taglist>
 
-        <p>A session opened in this way is closed using function
-          <seeerl marker="#close">close</seeerl>.</p>
 
        <marker id="pwd"></marker>
       </desc>
     </func>
 
     <func>
-      <name since="">pwd(Pid) -> {ok, Dir} | {error, Reason}</name>
+      <name since="" name="pwd" arity="1"/>
       <fsummary>Gets the remote current working directory.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Reason = restriction_reason() | common_reason()</v>
-      </type>
       <desc>
         <p>Returns the current working directory at the remote server.</p>
 
@@ -614,23 +521,17 @@
     </func>
 
     <func>
-      <name since="">recv(Pid, RemoteFile) -> </name>
-      <name since="">recv(Pid, RemoteFile, LocalFile) -> ok | {error, Reason}</name>
+      <name since="" name="recv" arity="2"/>
+      <name since="" name="recv" arity="3"/>
       <fsummary>Transfers a file from remote server.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>RemoteFile = LocalFile = string()</v>
-        <v>Reason = restriction_reason() | common_reason() | file_write_error_reason() </v>
-        <v>file_write_error_reason() = see file:write/2</v>
-      </type>
-      <desc>
-        <p>Transfers the file <c>RemoteFile</c> from the remote server
+      <desc>
+        <p>Transfers the file <c><anno>RemoteFileName</anno></c> from the remote server
           to the file system of the local client. If
-          <c>LocalFile</c> is specified, the local file will be
-          <c>LocalFile</c>, otherwise
-          <c>RemoteFile</c>.</p>
-        <p>If the file write fails (for example, <c>enospc</c>), the command is 
-	aborted and <c>{error, file_write_error_reason()}</c> is returned. 
+          <c><anno>LocalFileName</anno></c> is specified, the local file will be
+          <c><anno>LocalFileName</anno></c>, otherwise
+          <c><anno>RemoteFileName</anno></c>.</p>
+        <p>If the file write fails, the command is
+	aborted and <c>{error, term()}</c> is returned.
 	However, the file is <em>not</em> removed.</p>
 
        <marker id="recv_bin"></marker>
@@ -638,16 +539,10 @@
     </func>
 
     <func>
-      <name since="">recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, Reason}</name>
+      <name since="" name="recv_bin" arity="2"/>
       <fsummary>Transfers a file from remote server as a binary.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Bin = binary()</v>
-        <v>RemoteFile = string()</v>
-        <v>Reason = restriction_reason() | common_reason()</v>
-      </type>
-      <desc>
-        <p>Transfers the file <c>RemoteFile</c> from the remote server and 
+      <desc>
+        <p>Transfers the file <c><anno>RemoteFile</anno></c> from the remote server and
           receives it as a binary.</p>
 
        <marker id="recv_chunk_start"></marker>
@@ -655,15 +550,10 @@
     </func>
 
     <func>
-      <name since="">recv_chunk_start(Pid, RemoteFile) -> ok | {error, Reason}</name>
+      <name since="" name="recv_chunk_start" arity="2"/>
       <fsummary>Starts chunk-reading of the remote file.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>RemoteFile = string()</v>
-        <v>Reason = restriction_reason() | common_reason()</v>
-      </type>
       <desc>
-        <p>Starts transfer of the file <c>RemoteFile</c> from the
+        <p>Starts transfer of the file <c><anno>RemoteFile</anno></c> from the
           remote server.</p>
 
        <marker id="recv_chunk"></marker>
@@ -671,15 +561,10 @@
     </func>
 
     <func>
-      <name since="">recv_chunk(Pid) -> ok | {ok, Bin} | {error, Reason}</name>
+      <name since="" name="recv_chunk" arity="1" />
       <fsummary>Receives a chunk of the remote file.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Bin = binary()</v>
-        <v>Reason = restriction_reason() | common_reason()</v>
-      </type>
       <desc>
-        <p>Receives a chunk of the remote file (<c>RemoteFile</c> of 
+        <p>Receives a chunk of the remote file (<c>RemoteFile</c> of
           <c>recv_chunk_start</c>). The return values have the following 
           meaning:</p>
         <list type="bulleted">
@@ -693,30 +578,20 @@
     </func>
 
     <func>
-      <name since="">rename(Pid, Old, New) -> ok | {error, Reason}</name>
+      <name since="" name="rename" arity="3"/>
       <fsummary>Renames a file at the remote server.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>CurrFile = NewFile = string()</v>
-        <v>Reason = restriction_reason() | common_reason()</v>
-      </type>
       <desc>
-        <p>Renames <c>Old</c> to <c>New</c> at the remote server.</p>
+        <p>Renames <c><anno>Old</anno></c> to <c><anno>New</anno></c> at the remote server.</p>
 
        <marker id="rmdir"></marker>
       </desc>
     </func>
 
     <func>
-      <name since="">rmdir(Pid, Dir) -> ok | {error, Reason}</name>
+      <name since="" name="rmdir" arity="2" />
       <fsummary>Removes a remote directory.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Dir = string()</v>
-        <v>Reason = restriction_reason() | common_reason()</v>
-      </type>
       <desc>
-        <p>Removes directory <c>Dir</c> at the remote server.</p>
+        <p>Removes directory <c><anno>Dir</anno></c> at the remote server.</p>
 
        <marker id="send"></marker>
        <marker id="send2"></marker>
@@ -725,34 +600,23 @@
     </func>
 
     <func>
-      <name since="">send(Pid, LocalFile) -></name>
-      <name since="">send(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name>
+      <name since="" name="send" arity="2"/>
+      <name since="" name="send" arity="3"/>
       <fsummary>Transfers a file to the remote server.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>LocalFile = RemoteFile = string()</v>
-        <v>Reason = restriction_reason() | common_reason() | shortage_reason()</v>
-      </type>
       <desc>
-        <p>Transfers the file <c>LocalFile</c> to the remote server. If 
-          <c>RemoteFile</c> is specified, the name of the remote file is set
-          to <c>RemoteFile</c>, otherwise to <c>LocalFile</c>.</p>
+        <p>Transfers the file <c><anno>LocalFileName</anno></c> to the remote server. If
+          <c><anno>RemoteFileName</anno></c> is specified, the name of the remote file is set
+          to <c><anno>RemoteFileName</anno></c>, otherwise to <c><anno>LocalFileName</anno></c>.</p>
 
        <marker id="send_bin"></marker>
       </desc>
     </func>
 
     <func>
-      <name since="">send_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
+      <name since="" name="send_bin" arity="3" />
       <fsummary>Transfers a binary into a remote file.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Bin = binary()</v>
-        <v>RemoteFile = string()</v>
-        <v>Reason = restriction_reason() | common_reason() | shortage_reason()</v>
-      </type>
-      <desc>
-        <p>Transfers the binary <c>Bin</c> into the file <c>RemoteFile</c> 
+      <desc>
+        <p>Transfers the binary <c><anno>Bin</anno></c> into the file <c><anno>RemoteFile</anno></c>
           at the remote server.</p>
 
        <marker id="send_chunk"></marker>
@@ -760,15 +624,10 @@
     </func>
 
     <func>
-      <name since="">send_chunk(Pid, Bin) -> ok | {error, Reason}</name>
+      <name since="" name="send_chunk" arity="2" />
       <fsummary>Writes a chunk to the remote file.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Bin = binary()</v>
-        <v>Reason = echunk | restriction_reason() | common_reason()</v>
-      </type>
       <desc>
-        <p>Transfers the chunk <c>Bin</c> to the remote server, which 
+        <p>Transfers the chunk <c><anno>Bin</anno></c> to the remote server, which
           writes it into the file specified in the call to 
           <c>send_chunk_start/2</c>.</p>
         <p>For some errors, for example, file system full, it is 
@@ -780,15 +639,10 @@
     </func>
 
     <func>
-      <name since="">send_chunk_start(Pid, File) -> ok | {error, Reason}</name>
+      <name since="" name="send_chunk_start" arity="2" />
       <fsummary>Starts transfer of file chunks.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>File = string()</v>
-        <v>Reason = restriction_reason() | common_reason()</v>
-      </type>
       <desc>
-        <p>Starts transfer of chunks into the file <c>File</c> at the 
+        <p>Starts transfer of chunks into the file <c><anno>RemoteFile</anno></c> at the
           remote server.</p>
 
        <marker id="send_chunk_end"></marker>
@@ -796,12 +650,8 @@
     </func>
 
     <func>
-      <name since="">send_chunk_end(Pid) -> ok | {error, Reason}</name>
+      <name since="" name="send_chunk_end" arity="1" />
       <fsummary>Stops transfer of chunks.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Reason = restriction_reason() | common_reason() | shortage_reason()</v>
-      </type>
       <desc>
         <p>Stops transfer of chunks to the remote server. The file at the
           remote server, specified in the call to <c>send_chunk_start/2</c> 
@@ -812,14 +662,9 @@
     </func>
 
     <func>
-      <name since="OTP 21.0">start_service(ServiceConfig) -> {ok, Pid} | {error, Reason}</name>
+      <name since="OTP 21.0" name="start_service" arity="1" />
       <fsummary>Dynamically starts an <c>FTP</c>
       session after the <c>ftp</c> application has been started.</fsummary>
-      <type>
-        <v>ServiceConfig = [{Option, Value}]</v>
-	<v>Option = property()</v>
-	<v>Value = term()</v>
-      </type>
       <desc>
         <p>Dynamically starts an <c>FTP</c> session after the <c>ftp</c>
           application has been started.</p>
@@ -831,25 +676,19 @@
     </func>
 
     <func>
-      <name since="OTP 21.0">stop_service(Reference) -> ok | {error, Reason} </name>
+      <name since="OTP 21.0" name="stop_service" arity="1"/>
       <fsummary>Stops an FTP session.</fsummary>
-      <type>
-	<v>Reference = pid() | term() - service-specified reference</v>
-	<v>Reason = term()</v>
-      </type>
+      <type_desc variable="Pid">
+        Service-specified reference
+      </type_desc>
       <desc>
         <p>Stops a started FTP session.</p>
       </desc>
     </func>
 
     <func>
-      <name since="">type(Pid, Type) -> ok | {error, Reason}</name>
+      <name since="" name="type" arity="2" />
       <fsummary>Sets transfer type to <c>ascii</c>or <c>binary</c>.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Type = ascii | binary</v>
-        <v>Reason = etype | restriction_reason() | common_reason()</v>
-      </type>
       <desc>
         <p>Sets the file transfer type to <c>ascii</c> or <c>binary</c>. When
           an FTP session is opened, the default transfer type of the
@@ -860,45 +699,30 @@
     </func>
 
     <func>
-      <name since="">user(Pid, User, Password) -> ok | {error, Reason}</name>
+      <name since="" name="user" arity="3"/>
       <fsummary>User login.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>User = Password = string()</v>
-        <v>Reason = euser | common_reason()</v>
-      </type>
       <desc>
-        <p>Performs login of <c>User</c> with <c>Password</c>.</p>
+        <p>Performs login of <c><anno>User</anno></c> with <c><anno>Pass</anno></c>.</p>
 
        <marker id="user4"></marker>
       </desc>
     </func>
 
     <func>
-      <name since="">user(Pid, User, Password, Account) -> ok | {error, Reason}</name>
+      <name since="" name="user" arity="4" />
       <fsummary>User login.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>User = Password = string()</v>
-        <v>Reason = euser | common_reason() </v>
-      </type>
       <desc>
-        <p>Performs login of <c>User</c> with <c>Password</c> to the account 
-          specified by <c>Account</c>.</p>
+        <p>Performs login of <c><anno>User</anno></c> with <c><anno>Pass</anno></c> to the account
+          specified by <c><anno>Account</anno></c>.</p>
 
        <marker id="quote"></marker>
       </desc>
     </func>
 
     <func>
-      <name since="">quote(Pid, Command) -> [FTPLine]</name>
+      <name since="" name="quote" arity="2" />
       <fsummary>Sends an arbitrary FTP command.</fsummary>
-      <type>
-        <v>Pid = pid()</v>
-        <v>Command = string()</v>
-        <v>FTPLine = string()</v>
-      </type>
-      <desc><note><p>The telnet end of line characters, from the FTP 
+      <desc><note><p>The telnet end of line characters, from the FTP
       protocol definition, CRLF, for example, "\\r\\n" has been removed.</p></note>
         <p>Sends an arbitrary FTP command and returns verbatim a list
           of the lines sent back by the FTP server. This function is
diff --git a/lib/ftp/doc/src/specs.xml b/lib/ftp/doc/src/specs.xml
new file mode 100644
index 0000000000..a01aa83f78
--- /dev/null
+++ b/lib/ftp/doc/src/specs.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<specs xmlns:xi="http://www.w3.org/2001/XInclude">
+  <xi:include href="../specs/specs_ftp.xml"/>
+</specs>
diff --git a/lib/ftp/test/ftp_SUITE.erl b/lib/ftp/test/ftp_SUITE.erl
index 961b3b5fe1..9239d18f74 100644
--- a/lib/ftp/test/ftp_SUITE.erl
+++ b/lib/ftp/test/ftp_SUITE.erl
@@ -1069,7 +1069,7 @@ error_ehost(_Config) ->
 %%%----------------------------------------------------------------
 error_datafail() ->
     [{doc, "Test that failure to open data channel captures "
-     "error emitted on ctrl chanenel"}].
+     "error emitted on ctrl channel"}].
 
 error_datafail(Config) ->
     Self = self(),
-- 
2.35.3

openSUSE Build Service is sponsored by