From 06021f67883d6ad5eb1e65725043b7b88ed97498 Mon Sep 17 00:00:00 2001 From: Anthony Gauchy <anthony.gauchy@belledonne-communications.com> Date: Wed, 3 Feb 2021 18:08:44 +0100 Subject: [PATCH] CSharp wrapper documentation improvment - Update python wrapper generation to genrate LinphoneWrapper.cs with a full documentation. - Update cmake to generate a html doc site with docfx for C# wrapper - minor fixes in lib documentation --- coreapi/help/doc/doxygen/doxygen.dox.in | 2 +- include/linphone/types.h | 4 +- tools/metadoc.py | 103 ++++++++++++++++++--- wrappers/cpp/doxygen.dox.in | 2 +- wrappers/csharp/CMakeLists.txt | 62 +++++++++++++ wrappers/csharp/docfx_project/api/index.md | 1 + wrappers/csharp/docfx_project/docfx.json | 44 +++++++++ wrappers/csharp/docfx_project/index.md | 25 +++++ wrappers/csharp/docfx_project/toc.yml | 4 + wrappers/csharp/genwrapper.py | 18 +++- wrappers/csharp/version.txt.in | 2 + wrappers/csharp/wrapper_impl.mustache | 85 ++++++++++++++--- 12 files changed, 321 insertions(+), 31 deletions(-) create mode 100644 wrappers/csharp/docfx_project/api/index.md create mode 100644 wrappers/csharp/docfx_project/docfx.json create mode 100644 wrappers/csharp/docfx_project/index.md create mode 100644 wrappers/csharp/docfx_project/toc.yml create mode 100644 wrappers/csharp/version.txt.in diff --git a/coreapi/help/doc/doxygen/doxygen.dox.in b/coreapi/help/doc/doxygen/doxygen.dox.in index 2b7f916ebb..40d97b622c 100644 --- a/coreapi/help/doc/doxygen/doxygen.dox.in +++ b/coreapi/help/doc/doxygen/doxygen.dox.in @@ -15,7 +15,7 @@ * - C++ (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/c++) * - Swift (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/swift) * - Java (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/java) - * - C# (coming soon) + * - C# (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/cs) * - Python (coming soon) * * Liblinphone is distributed under GPLv3 (https://www.gnu.org/licenses/gpl-3.0.html). Please understand the licencing details before using it! diff --git a/include/linphone/types.h b/include/linphone/types.h index d5625b79b4..7c415da595 100644 --- a/include/linphone/types.h +++ b/include/linphone/types.h @@ -205,8 +205,8 @@ typedef enum _LinphoneAccountCreatorStatus { LinphoneAccountCreatorStatusPhoneNumberInvalid, /**< Error cannot send SMS */ LinphoneAccountCreatorStatusWrongActivationCode, /**< Error key doesn't match */ LinphoneAccountCreatorStatusPhoneNumberOverused, /**< Error too many SMS sent */ - LinphoneAccountCreatorStatusAlgoNotSupported, /** < Error algo isn't MD5 or SHA-256 */ - LinphoneAccountCreatorStatusUnexpectedError, /** < Generic error */ + LinphoneAccountCreatorStatusAlgoNotSupported, /**< Error algo isn't MD5 or SHA-256 */ + LinphoneAccountCreatorStatusUnexpectedError, /**< Generic error */ } LinphoneAccountCreatorStatus; // ----------------------------------------------------------------------------- diff --git a/tools/metadoc.py b/tools/metadoc.py index d0d5b10961..81fe46668c 100644 --- a/tools/metadoc.py +++ b/tools/metadoc.py @@ -22,6 +22,7 @@ import abstractapi import logging import metaname import re +import sys class ParsingError(RuntimeError): @@ -509,19 +510,29 @@ class Translator: lines = [] while len(line) > width: cutIndex = line.rfind(' ', 0, width) - if cutIndex != -1: + if cutIndex >= 0: if self.langCode == 'Java': # Do not break a line in the middle of a { } while (not line[0:cutIndex].count('{') == line[0:cutIndex].count('}')) and (not line[cutIndex:].count('{') == line[cutIndex:].count('}')): cutIndex += 1 - lines.append(line[0:cutIndex]) - line = line[cutIndex+1:] + if self.langCode == 'CSharp': + # Do not break a line in the middle of a xml tag + while not line[0:cutIndex].count('<') == line[0:cutIndex].count('>'): + cutIndex += 1 + if line[cutIndex] == ' ': + # Don't keep a whitespace at the start of the next line if you cut on one + lines.append(line[0:cutIndex]) + line = line[cutIndex+1:] + else: + lines.append(line[0:cutIndex]) + line = line[cutIndex:] else: # Don't break http links - cutIndex = len(line) if ('http://' or 'https://') in line else width + cutIndex = len(line) if 'http://' or 'https://' in line else width lines.append(line[0:cutIndex]) line = line[cutIndex:] - lines.append(line) + if line: + lines.append(line) if indent: lines = [line if line is lines[0] else '\t' + line for line in lines] @@ -784,15 +795,85 @@ class SphinxTranslator(Translator): class SandCastleTranslator(Translator): - def _tag_as_brief(self, lines): - if len(lines) > 0: - lines.insert(0, '<summary>') - lines.append('</summary>') + def __init__(self, langCode): + super().__init__(langCode) + self.isEndTagPlaced = False + + def translate_text(self, textpart): + text = super().translate_text(textpart) + xmlSpecialCharDict = {'<': '<', + '>': '>', + "'": ''', + '"': '"', + '&': '&'} + if sys.version_info[0] >= 3: + xmlTranslationTable = str.maketrans(xmlSpecialCharDict) + else: + import string + xmlTranslationTable = string.maketrans(xmlSpecialCharDict) + return text.translate(xmlTranslationTable) + + def translate_description(self, description, tagAsBrief=False): + self.isEndTagPlaced = False + translatedDoc = super().translate_description(description, tagAsBrief) + if not tagAsBrief: + if not self.isEndTagPlaced: + translatedDoc['lines'].append({'line': '</para>'}) + translatedDoc['lines'].append({'line': '</summary>'}) + self.isEndTagPlaced = True + return translatedDoc def translate_function_reference(self, ref): refStr = Translator.translate_reference(self, ref, absName=True) - return '<see cref="{0}()" />'.format(refStr) + subnResult = re.subn('(\.Get\(\))', '.Instance' , refStr) + if subnResult[1] > 0: + return '<see cref="{0}">{0}</see>'.format(subnResult[0]) + subnResult = re.subn('(\.Get|\.Set)', '.' , subnResult[0]) + if subnResult[1] > 0: + return '<see cref="{0}">{0}</see>'.format(subnResult[0]) + return '<see cref="{0}()">{0}()</see>'.format(subnResult[0]) + # In every cases we write the same value in the "see" tag value + # than in the cref value so that even if the cref is broken the + # text is displayed def translate_class_reference(self, ref): refStr = Translator.translate_reference(self, ref, absName=True) - return '<see cref="{0}" />'.format(refStr) + return '<see cref="{0}">{0}</see>'.format(refStr) + + + def _translate_parameter_list(self, parameterList): + text = '' + if not self.isEndTagPlaced: + text += '</para>\n' + text += '</summary>\n' + self.isEndTagPlaced = True + + for paramDesc in parameterList.parameters: + if self.displaySelfParam or not paramDesc.is_self_parameter(): + desc = self._translate_description(paramDesc.desc) + desc = desc[0] if len(desc) > 0 else '' + text += ('<param name="{0}">{1}</param>\n'.format(paramDesc.name.translate(self.nameTranslator), desc)) + return text + + def _translate_section(self, section): + text ='' + if not self.isEndTagPlaced: + text += '</para>\n' + text += '</summary>\n' + self.isEndTagPlaced = True + + if section.kind == 'return': + section.kind = '<returns>{0}</returns>' + elif section.kind == 'warning': + section.kind = '<remarks>Warning : {0}</remarks> ' + elif section.kind == 'note': + section.kind = '<remarks>Note : {0}</remarks>' + elif section.kind == 'see': + section.kind = '<remarks>See : {0}</remarks>' + else: + section.kind = section.kind + " : {0}" + logging.warning('SandCastle doc translate section pointing on an unknown object ({0})'.format(section.kind)) + + text += section.kind.format(self._translate_paragraph(section.paragraph)) + + return text diff --git a/wrappers/cpp/doxygen.dox.in b/wrappers/cpp/doxygen.dox.in index 8601ad5da4..e9687e7b74 100644 --- a/wrappers/cpp/doxygen.dox.in +++ b/wrappers/cpp/doxygen.dox.in @@ -15,7 +15,7 @@ * - C++ (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/c++) * - Swift (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/swift) * - Java (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/java) - * - C# (coming soon) + * - C# (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/cs) * - Python (coming soon) * * Liblinphone is distributed under GPLv3 (https://www.gnu.org/licenses/gpl-3.0.html). Please understand the licencing details before using it! diff --git a/wrappers/csharp/CMakeLists.txt b/wrappers/csharp/CMakeLists.txt index d5acfcaa42..620bb6299f 100644 --- a/wrappers/csharp/CMakeLists.txt +++ b/wrappers/csharp/CMakeLists.txt @@ -38,3 +38,65 @@ add_custom_target(linphonecs ALL DEPENDS LinphoneWrapper.cs) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/LinphoneWrapper.cs" DESTINATION "${CMAKE_INSTALL_DATADIR}/linphonecs/" ) + +if(ENABLE_DOC AND ENABLE_CSHARP_WRAPPER) + message(CHECK_START "Generate C# wrapper html site") + find_program( + DOCFX_EXECUTABLE + NAMES docfx + ) + + if(DOCFX_EXECUTABLE) + file( + COPY "${CMAKE_CURRENT_SOURCE_DIR}/docfx_project" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/" + ) + + # Check git describe to see if we are on a release or not + set(LINPHONE_STATE "snapshots") + execute_process(COMMAND ${GIT_EXECUTABLE} describe OUTPUT_VARIABLE GIT_DESC) + if(NOT GIT_DESC MATCHES ".*(alpha|beta).*") + set(LINPHONE_STATE "releases") + endif() + # Replace @LINPHONE_STATE@ and @LINPHONE_VERSION@ in site main page + configure_file( + "${CMAKE_CURRENT_BINARY_DIR}/docfx_project/index.md" + "${CMAKE_CURRENT_BINARY_DIR}/docfx_project/index.md" + ) + # Replace @LINPHONE_STATE@ and @LINPHONE_VERSION@ version.txt.in, this + # file will be used in doc upload + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/version.txt.in" + "${CMAKE_CURRENT_BINARY_DIR}/version.txt" + ) + + add_custom_command(OUTPUT "docfx_project/_site/index.html" + DEPENDS LinphoneWrapper.cs + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_BINARY_DIR}/LinphoneWrapper.cs" + "${CMAKE_CURRENT_BINARY_DIR}/docfx_project/src/LinphoneWrapper.cs" + COMMAND ${DOCFX_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/docfx_project/docfx.json" + ) + + add_custom_target( + linphonecs-doc-site + ALL + DEPENDS linphonecs "docfx_project/_site/index.html" + ) + + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/version.txt" + DESTINATION "${CMAKE_INSTALL_DATADIR}/" + ) + + install( + DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/docfx_project/_site/" + DESTINATION "${CMAKE_INSTALL_DATADIR}/doc/liblinphone-${LINPHONE_VERSION}/cs" + ) + + message(CHECK_PASS "generation and copy configured") + else() + message(CHECK_FAIL "docfx wansn't found, no site generated") + + endif() +endif() diff --git a/wrappers/csharp/docfx_project/api/index.md b/wrappers/csharp/docfx_project/api/index.md new file mode 100644 index 0000000000..69deb01cd6 --- /dev/null +++ b/wrappers/csharp/docfx_project/api/index.md @@ -0,0 +1 @@ +# Liblinphone C# API documentation diff --git a/wrappers/csharp/docfx_project/docfx.json b/wrappers/csharp/docfx_project/docfx.json new file mode 100644 index 0000000000..c21402f4e5 --- /dev/null +++ b/wrappers/csharp/docfx_project/docfx.json @@ -0,0 +1,44 @@ +{ + "metadata": [ + { + "src": [ + { + "files": [ + "src/**.cs" + ] + } + ], + "dest": "api", + "disableGitFeatures": false, + "disableDefaultFilter": false + } + ], + "build": { + "content": [ + { + "files": [ + "api/**.yml", + "api/index.md" + ] + }, + { + "files": [ + "toc.yml", + "*.md" + ] + } + ], + "dest": "_site", + "globalMetadataFiles": [], + "fileMetadataFiles": [], + "template": [ + "statictoc" + ], + "postProcessors": [], + "markdownEngineName": "markdig", + "noLangKeyword": false, + "keepFileLink": false, + "cleanupCacheHistory": false, + "disableGitFeatures": false + } +} \ No newline at end of file diff --git a/wrappers/csharp/docfx_project/index.md b/wrappers/csharp/docfx_project/index.md new file mode 100644 index 0000000000..0cdcfa006d --- /dev/null +++ b/wrappers/csharp/docfx_project/index.md @@ -0,0 +1,25 @@ +# Liblinphone Documentation + +## What is liblinphone + +Liblinphone is a high-level open source library that integrates all the SIP voice/video and instant messaging features into a single easy-to-use API. This is the VoIP SDK engine on which Linphone applications are based. + +Liblinphone combines our media processing and streaming toolkit (Mediastreamer2) with our user-agent library for SIP signaling (belle-sip). Liblinphone has support for a variety of languages, each one has its own reference documentation: + + - C (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/c) + - C++ (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/c++) + - Swift (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/swift) + - Java (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/java) + - C# (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/cs) + - Python (coming soon) + +Liblinphone is distributed under GPLv3 (https://www.gnu.org/licenses/gpl-3.0.html). Please understand the licencing details before using it! + +For any use of this library beyond the rights granted to you by the GPLv3 license, please [contact Belledonne Communications](https://www.linphone.org/contact). + +## CSharp tutorial for liblinphone + +You can find a step by step tutorial to use liblinphone in C# [here](https://gitlab.linphone.org/BC/public/tutorials) + +## See also +http://www.linphone.org diff --git a/wrappers/csharp/docfx_project/toc.yml b/wrappers/csharp/docfx_project/toc.yml new file mode 100644 index 0000000000..159adc35a6 --- /dev/null +++ b/wrappers/csharp/docfx_project/toc.yml @@ -0,0 +1,4 @@ +- name: Api Documentation + href: api/ + # Default was api/index.md but we prefer to jump directly to api/Linphone.html page + homepage: api/Linphone.html diff --git a/wrappers/csharp/genwrapper.py b/wrappers/csharp/genwrapper.py index c3f652d4a2..0c1a530d97 100644 --- a/wrappers/csharp/genwrapper.py +++ b/wrappers/csharp/genwrapper.py @@ -90,7 +90,8 @@ class CsharpTranslator: methodDict = {} methodDict['prototype'] = "static extern {return} {name}({params});".format(**methodElems) - methodDict['doc'] = method.briefDescription.translate(self.docTranslator, tagAsBrief=True) + methodDict['briefDoc'] = method.briefDescription.translate(self.docTranslator, tagAsBrief=True) if method.briefDescription is not None else None + methodDict['detailedDoc'] = method.detailedDescription.translate(self.docTranslator) if method.detailedDescription is not None else None methodDict['has_impl'] = genImpl if genImpl: @@ -294,6 +295,10 @@ class CsharpTranslator: listenerDict['delegate']['params_private'] += dllImportType + " " + argName listenerDict['delegate']["c_name_setter"] = c_name_setter + + listenerDict['delegate']['briefDoc'] = method.briefDescription.translate(self.docTranslator, tagAsBrief=True) if method.briefDescription is not None else None + listenerDict['delegate']['detailedDoc'] = method.detailedDescription.translate(self.docTranslator) if method.detailedDescription is not None else None + return listenerDict ########################################################################################################################################### @@ -301,7 +306,8 @@ class CsharpTranslator: def translate_enum(self, enum): enumDict = {} enumDict['enumName'] = enum.name.translate(self.nameTranslator) - enumDict['doc'] = enum.briefDescription.translate(self.docTranslator, tagAsBrief=True) + enumDict['briefDoc'] = enum.briefDescription.translate(self.docTranslator, tagAsBrief=True) + enumDict['detailedDoc'] = enum.detailedDescription.translate(self.docTranslator) enumDict['values'] = [] enumDict['isFlag'] = False i = 0 @@ -309,7 +315,8 @@ class CsharpTranslator: for enumValue in enum.enumerators: enumValDict = {} enumValDict['name'] = enumValue.name.translate(self.nameTranslator) - enumValDict['doc'] = enumValue.briefDescription.translate(self.docTranslator, tagAsBrief=True) + enumValDict['briefDoc'] = enumValue.briefDescription.translate(self.docTranslator, tagAsBrief=True) + enumValDict['detailedDoc'] = enumValue.detailedDescription.translate(self.docTranslator) if isinstance(enumValue.value, int): lastValue = enumValue.value enumValDict['value'] = str(enumValue.value) @@ -335,7 +342,8 @@ class CsharpTranslator: classDict['isLinphoneFactory'] = classDict['className'] == "Factory" classDict['isLinphoneCall'] = _class.name.to_camel_case() == "Call" classDict['isLinphoneCore'] = _class.name.to_camel_case() == "Core" - classDict['doc'] = _class.briefDescription.translate(self.docTranslator, tagAsBrief=True) + classDict['briefDoc'] = _class.briefDescription.translate(self.docTranslator, tagAsBrief=True) + classDict['detailedDoc'] = _class.detailedDescription.translate(self.docTranslator) classDict['dllImports'] = [] islistenable = _class.listenerInterface is not None @@ -382,6 +390,8 @@ class CsharpTranslator: interfaceDict['interfaceName'] = interface.name.translate(self.nameTranslator) interfaceDict['set_user_data_name'] = interface.listenedClass.name.to_snake_case(fullName=True) + '_cbs_set_user_data' interfaceDict['get_user_data_name'] = interface.listenedClass.name.to_snake_case(fullName=True) + '_cbs_get_user_data' + interfaceDict['briefDoc'] = interface.briefDescription.translate(self.docTranslator, tagAsBrief=True) + interfaceDict['detailedDoc'] = interface.detailedDescription.translate(self.docTranslator) interfaceDict['methods'] = [] for method in interface.instanceMethods: diff --git a/wrappers/csharp/version.txt.in b/wrappers/csharp/version.txt.in new file mode 100644 index 0000000000..3b419d8d49 --- /dev/null +++ b/wrappers/csharp/version.txt.in @@ -0,0 +1,2 @@ +LINPHONE_VERSION=@LINPHONE_VERSION@ +LINPHONE_STATE=@LINPHONE_STATE@ \ No newline at end of file diff --git a/wrappers/csharp/wrapper_impl.mustache b/wrappers/csharp/wrapper_impl.mustache index 811cfbfaa4..83e64ec180 100644 --- a/wrappers/csharp/wrapper_impl.mustache +++ b/wrappers/csharp/wrapper_impl.mustache @@ -288,21 +288,37 @@ namespace Linphone #endregion #region Enums + {{#enums}} {{#enum}} - {{#doc}} + /// <summary> + {{#briefDoc}} + {{#lines}} + /// {{{line}}} + {{/lines}} + {{/briefDoc}} + {{#detailedDoc}} + /// <para> {{#lines}} /// {{{line}}} {{/lines}} - {{/doc}}{{#isFlag}}[Flags]{{/isFlag}} + {{/detailedDoc}} + {{#isFlag}}[Flags]{{/isFlag}} public enum {{enumName}} { {{#values}} - {{#doc}} + /// <summary> + {{#briefDoc}} + {{#lines}} + /// {{{line}}} + {{/lines}} + {{/briefDoc}} + {{#detailedDoc}} + /// <para> {{#lines}} /// {{{line}}} {{/lines}} - {{/doc}} + {{/detailedDoc}} {{name}} = {{{value}}}, {{/values}} } @@ -314,6 +330,18 @@ namespace Linphone #region Listeners {{#interfaces}} {{#interface}} + /// <summary> + {{#briefDoc}} + {{#lines}} + /// {{{line}}} + {{/lines}} + {{/briefDoc}} + {{#detailedDoc}} + /// <para> + {{#lines}} + /// {{{line}}} + {{/lines}} + {{/detailedDoc}} [StructLayout(LayoutKind.Sequential)] public class {{interfaceName}} : LinphoneObject { @@ -342,7 +370,20 @@ namespace Linphone [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void {{name_private}}({{params_private}}); - public delegate void {{name_public}}({{{params_public}}}); + /// <summary> + {{#briefDoc}} + {{#lines}} + /// {{{line}}} + {{/lines}} + {{/briefDoc}} + {{#detailedDoc}} + /// <para> + {{#lines}} + /// {{{line}}} + {{/lines}} + {{/detailedDoc}} + {{#deprecated}}[Obsolete] + {{/deprecated}}public delegate void {{name_public}}({{{params_public}}}); private {{name_private}} {{var_private}}; private {{name_public}} {{var_public}}; @@ -419,12 +460,18 @@ namespace Linphone #region Classes {{#classes}} {{#_class}} - {{#doc}} + /// <summary> + {{#briefDoc}} {{#lines}} /// {{{line}}} {{/lines}} - {{/doc}} - + {{/briefDoc}} + {{#detailedDoc}} + /// <para> + {{#lines}} + /// {{{line}}} + {{/lines}} + {{/detailedDoc}} [StructLayout(LayoutKind.Sequential)] public class {{className}} : LinphoneObject { @@ -554,11 +601,18 @@ namespace Linphone {{/has_second_prototype}} {{#has_property}} - {{#doc}} + /// <summary> + {{#briefDoc}} + {{#lines}} + /// {{{line}}} + {{/lines}} + {{/briefDoc}} + {{#detailedDoc}} + /// <para> {{#lines}} /// {{{line}}} {{/lines}} - {{/doc}} + {{/detailedDoc}} {{property_static}}public {{{property_return}}} {{property_name}} { {{#has_getter}} @@ -628,11 +682,18 @@ namespace Linphone {{/has_property}} {{#has_impl}} {{#impl}} - {{#doc}} + /// <summary> + {{#briefDoc}} + {{#lines}} + /// {{{line}}} + {{/lines}} + {{/briefDoc}} + {{#detailedDoc}} + /// <para> {{#lines}} /// {{{line}}} {{/lines}} - {{/doc}} + {{/detailedDoc}} public {{static}}{{override}}{{{type}}} {{name}}({{{args}}}) { {{#is_string}} -- GitLab