From 86dcb31ab36dafbf512c45c800f22b7db8c13440 Mon Sep 17 00:00:00 2001 From: Wen Liang Date: Thu, 22 Apr 2021 18:41:50 -0400 Subject: [PATCH] build: replace `./tools/generate-docs-nm-property-infos.pl` with python script In order to have more structured settings in man page and make it more manageable to generate the docbook, it is recommended to use python script to replace `./tools/generate-docs-nm-property-infos.pl` (this tool is used to parse the comment section starting with `---nmcli---`, `---dbus---`, `---keyfile---`, `---ifcfg-rh---`). Signed-off-by: Wen Liang https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/824 --- Makefile.am | 6 +- src/libnm-client-impl/meson.build | 4 +- tools/generate-docs-nm-property-infos.pl | 160 ----------------------- tools/generate-docs-nm-property-infos.py | 146 +++++++++++++++++++++ 4 files changed, 151 insertions(+), 165 deletions(-) delete mode 100755 tools/generate-docs-nm-property-infos.pl create mode 100755 tools/generate-docs-nm-property-infos.py diff --git a/Makefile.am b/Makefile.am index 1f74709ffc..1c2fa24c23 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1890,8 +1890,8 @@ endif libnm_docs_sources = $(src_libnm_core_impl_lib_c_settings_real) -src/libnm-client-impl/nm-property-infos-%.xml: tools/generate-docs-nm-property-infos.pl $(libnm_docs_sources) - $(AM_V_GEN) $(srcdir)/tools/generate-docs-nm-property-infos.pl $(patsubst nm-property-infos-%.xml,%,$(notdir $@)) $@ $(filter-out $<,$^) +src/libnm-client-impl/nm-property-infos-%.xml: tools/generate-docs-nm-property-infos.py $(libnm_docs_sources) + $(AM_V_GEN) "$(PYTHON)" $(srcdir)/tools/generate-docs-nm-property-infos.py $(patsubst nm-property-infos-%.xml,%,$(notdir $@)) $@ $(filter-out $<,$^) src/libnm-client-impl/nm-settings-docs-gir.xml: tools/generate-docs-nm-settings-docs-gir.py src/libnm-client-impl/NM-1.0.gir src/libnm-client-impl/NM-1.0.typelib src/libnm-client-impl/libnm.la $(libnm_docs_sources) $(AM_V_GEN) \ @@ -1918,7 +1918,7 @@ DISTCLEANFILES += $(libnm_noinst_data) endif EXTRA_DIST += \ - tools/generate-docs-nm-property-infos.pl \ + tools/generate-docs-nm-property-infos.py \ tools/generate-docs-nm-settings-docs-merge.py \ tools/generate-docs-nm-settings-docs-gir.py \ src/libnm-client-impl/meson.build \ diff --git a/src/libnm-client-impl/meson.build b/src/libnm-client-impl/meson.build index 4682e2baf2..21a01e0b04 100644 --- a/src/libnm-client-impl/meson.build +++ b/src/libnm-client-impl/meson.build @@ -162,8 +162,8 @@ if enable_introspection input: libnm_core_settings_sources, output: 'nm-propery-infos-' + info + '.xml', command: [ - perl, - join_paths(meson.source_root(), 'tools', 'generate-docs-nm-property-infos.pl'), + python.path(), + join_paths(meson.source_root(), 'tools', 'generate-docs-nm-property-infos.py'), info, '@OUTPUT@', '@INPUT@' diff --git a/tools/generate-docs-nm-property-infos.pl b/tools/generate-docs-nm-property-infos.pl deleted file mode 100755 index 7d33d140b4..0000000000 --- a/tools/generate-docs-nm-property-infos.pl +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env perl -# SPDX-License-Identifier: GPL-2.0-or-later -# -# Copyright (C) 2014 Red Hat, Inc. -# - -# -# The script parses nm-setting-*.c files and extracts documentation related -# to setting plugins. The documentation is in a simple format of lines -# "keyword: value". The documentation is enclosed between tags -# ------ and ---end--- -# Recognized keywords are: -# "property: " - property name -# "variable: " - name of the variable used by the plugin -# "format: " - format of the value in 'keyfile' plugin -# "default: " - default value when variable is not used -# "values: " - allowed values (e.g. for enumerations) -# "example: " - example(s) -# "description: " - description text -# Value is an arbitrary string that can span over multiple lines. -# -# ifcfg-rh specifics: -# - mark NM extension variables with (+), e.g. variable: UUID(+) -# - -use strict; -use warnings; -use v5.10; - -# global variables -my @keywords = ("property", "variable", "format", "values", "default", "example", "description"); -my @data; -my $fo; - -(scalar @ARGV >= 3) or die "Usage: $0 \n"; -my ($plugin, $output, (@source_files)) = @ARGV; -my $start_tag = "---$plugin---\\s*\$"; -my $end_tag = '---end---'; - -# open output file -open $fo, '>', $output or die "Can't open $output: $!"; - -# write XML header -write_header(); - -# write generated documentation for each setting -foreach my $c_file (@source_files) { - my $setting_name = get_setting_name($c_file); - if ($setting_name) { - write_item(""); - scan_doc_comments($c_file, $start_tag, $end_tag); - write_item(""); - } -} - -# write XML footer -write_footer(); - -# close output file -close $fo; - - -### --- subroutines --- ### - -# get setting name from NM_SETTING_*_SETTING_NAME constant in C header file -sub get_setting_name { - my $path = $_[0]; - $path =~ s/\/libnm-core-impl\/nm-setting-(.*)\.c$/\/libnm-core-public\/nm-setting-$1.h/; # use header file to find out setting name - $path =~ s/\.c$/.h/; # use header file to find out setting name - open my $fh, '<', $path or die "Can't open $path: $!"; - while (my $line = <$fh>) { - if ($line =~ /NM_SETTING_.+SETTING_NAME\s+\"(\S+)\"/) { - return $1; - } - } -} - -# scan source setting file for documentation tags and write them to XML -sub scan_doc_comments { - my($setting_file, $start, $end) = @_; - open my $fi, '<', $setting_file or die "Can't open $setting_file: $!"; - while (<$fi>) { - if (/$start/ .. /$end/) { - next if /$start/; - if (/$end/) { - process_data(); - } else { - push @data, $_; - } - next; - } - # ignore text not inside marks - } - close $fi; -} - -# process plugin property documentation comments -sub process_data { - return if not @data; - my $kwd_pat = join("|", @keywords); - my %parsed_data; - my $this_key; - - foreach (@data) { - if (/^\s*\**\s+($kwd_pat):\s+(.*?)\s*$/) { - $this_key = $1; - $parsed_data{$this_key} = "$2\n"; - } elsif (/^\s*\**\s+(.*?)\s*$/) { - die "Extra mess in a comment: $_" unless $this_key; - $parsed_data{$this_key} .= "$1\n"; - } - } - - # now write a line into the XML - my $name = $parsed_data{property} // ""; - my $var = $parsed_data{variable} // $name; # fallback to "property: " - my $format = $parsed_data{format} // ""; - my $values = $parsed_data{values} // ""; - my $def = $parsed_data{default} // ""; - my $exam = $parsed_data{example} // ""; - my $desc = $parsed_data{description} // ""; - - chomp($name, $var, $format, $values, $def, $exam, $desc); - escape_xml_chars($name, $var, $format, $values, $def, $exam, $desc); - my $foo = sprintf("", - $name, $var, $format, $values, $def, $exam, $desc); - write_item($foo); - @data = (); -} - -# - XML handling - -sub write_header { - (my $header = - qq{ - }) =~ s/^ {7}//mg; - print {$fo} $header; -} - -sub write_footer { - my $footer = ""; - print {$fo} $footer; -} - -sub write_item { - my $str = join("", @_); - print {$fo} $str, "\n"; -} - -sub escape_xml_chars { - # http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined%5Fentities%5Fin%5FXML - foreach my $val (@_) { - $val =~ s/&/&/sg; - $val =~ s//>/sg; - $val =~ s/"/"/sg; - $val =~ s/'/'/sg; - } -} - diff --git a/tools/generate-docs-nm-property-infos.py b/tools/generate-docs-nm-property-infos.py new file mode 100755 index 0000000000..90f757728e --- /dev/null +++ b/tools/generate-docs-nm-property-infos.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later + +import os, re, sys + + +def get_setting_name(one_file): + setting_name = "" + assert re.match(r".*/libnm-core-impl/nm-setting-.*\.c$", one_file) + header_path = one_file.replace("libnm-core-impl", "libnm-core-public") + header_path = header_path.replace(".c", ".h") + try: + header_reader = open(header_path, "r") + except OSError: + print("Can not open header file: %s" % (header_path)) + exit(1) + + line = header_reader.readline() + while line != "": + setting_name_found = re.search(r"NM_SETTING_.+SETTING_NAME\s+\"(\S+)\"", line) + if setting_name_found: + setting_name = setting_name_found.group(1) + break + line = header_reader.readline() + header_reader.close() + return setting_name + + +def scan_doc_comments(plugin, outfile, file, start_tag, end_tag): + data = [] + push_flag = 0 + try: + file_reader = open(file, "r") + except OSError: + print("Can not open file: %s" % (file)) + exit(1) + + line = file_reader.readline() + while line != "": + if start_tag in line: + push_flag = 1 + elif end_tag in line and push_flag == 1: + push_flag = 0 + parsed_data = process_data(data) + if parsed_data: + write_data(outfile, parsed_data) + data = [] + elif push_flag == 1: + data.append(line) + line = file_reader.readline() + file_reader.close() + return + + +def process_data(data): + parsed_data = {} + if not data: + return parsed_data + keywords = [ + "property", + "variable", + "format", + "values", + "default", + "example", + "description", + ] + kwd_pat = "|".join(keywords) + keyword = "" + for line in data: + kwd_first_line_found = re.search( + r"^\s*\**\s+({}):\s+(.*?)\s*$".format(kwd_pat), line + ) + kwd_more_line_found = re.search(r"^\s*\**\s+(.*?)\s*$", line) + if kwd_first_line_found: + keyword = kwd_first_line_found.group(1) + value = kwd_first_line_found.group(2) + "\n" + parsed_data[keyword] = escape_xml_char(value) + elif kwd_more_line_found: + if not keyword: + print("Extra mess in a comment: %s" % (line)) + exit(1) + else: + value = kwd_more_line_found.group(1) + "\n" + parsed_data[keyword] += escape_xml_char(value) + for keyword in keywords: + if keyword == "variable" and keyword not in parsed_data: + parsed_data[keyword] = parsed_data["property"] + elif keyword not in parsed_data: + parsed_data[keyword] = "" + for key in parsed_data.keys(): + parsed_data[key] = parsed_data[key].rstrip() + return parsed_data + + +def write_data(outfile, parsed_data): + outfile.write( + '\n'.format( + parsed_data["property"], + parsed_data["variable"], + parsed_data["format"], + parsed_data["values"], + parsed_data["default"], + parsed_data["example"], + parsed_data["description"], + ) + ) + + +def escape_xml_char(text): + text = text.replace("&", "&") + text = text.replace("<", "<") + text = text.replace(">", ">") + text = text.replace('"', """) + text = text.replace("'", "'") + + return text + + +if len(sys.argv) < 4: + print("Usage: %s [plugin] [output-xml-file] [srcfiles]" % (sys.argv[0])) + exit(1) + +argv = list(sys.argv[1:]) +plugin, output, source_files = argv[0], argv[1], argv[2:] +start_tag = "---" + plugin + "---" +end_tag = "---end---" +outfile = open(output, mode="w") + +# write XML header +outfile.write("\n") +outfile.write(" ") + +for one_file in source_files: + setting_name = get_setting_name(one_file) + if setting_name: + outfile.write('\n') + scan_doc_comments(plugin, outfile, one_file, start_tag, end_tag) + outfile.write("\n") + + +# write XML footer +outfile.write("") + +# close output file +outfile.close()