Simple diagrams in documentation

This commit is contained in:
rfm 2024-08-07 16:12:22 +01:00
parent 726777bf6e
commit 72c975c543
6 changed files with 236 additions and 93 deletions

View file

@ -1,3 +1,15 @@
2024-08-07 Richard Frith-Macdonald <rfm@gnu.org>
* Tools/AGSHtml.m:
* Tools/GNUmakefile:
* config.mak.in:
* configure.ac:
* configure:
Preliminary changes add inheritance diagrams to class documentation.
Uses the graphviz 'dot' tool if it is available at configure time.
Just draws class/superclass relationship for now.
Should add protocols and prettify boxes later.
2024-07-25 Richard Frith-Macdonald <rfm@gnu.org>
* Headers/Foundation/NSHashTable.h:

View file

@ -40,6 +40,15 @@
#define GS_API_MACOSX 100000
#endif
#if defined(HAVE_DOT)
#define expandstringify(X) stringify(X)
#define stringify(X) #X
static NSString *graphviz = @ expandstringify(HAVE_DOT);
#else
static NSString *graphviz = nil;
#endif
static int XML_ELEMENT_NODE;
static int XML_ENTITY_REF_NODE;
static int XML_TEXT_NODE;
@ -57,6 +66,76 @@ static GSXMLNode *firstElement(GSXMLNode *nodes)
return [nodes nextElement];
}
static BOOL
graph(NSString *path, NSString *input, BOOL verbose)
{
BOOL didLaunch = NO;
BOOL didWrite = NO;
BOOL didSucceed = NO;
ENTER_POOL
NSTask *task = AUTORELEASE([[NSTask alloc] init]);
NSPipe *writePipe = [NSPipe pipe];
NSFileHandle *writeHandle = [writePipe fileHandleForWriting];
[task setLaunchPath: graphviz];
[task setArguments: [NSArray arrayWithObjects:
@"-Tsvg", @"-o", path, nil]];
[task setStandardInput: [writePipe fileHandleForReading]];
if (verbose)
{
NSLog(@"Graph source '%@'", input);
}
else
{
[task setStandardError: [NSFileHandle fileHandleWithNullDevice]];
[task setStandardOutput: [NSFileHandle fileHandleWithNullDevice]];
}
NS_DURING
{
[task launch];
didLaunch = YES;
}
NS_HANDLER
{
NSLog(@"Failed task '%@': %@", path, localException);
task = nil; // No need to terminate
}
NS_ENDHANDLER
if (YES == didLaunch)
{
NS_DURING
{
NSData *data;
data = [input dataUsingEncoding: NSUTF8StringEncoding];
[writeHandle writeData: data];
didWrite = YES;
}
NS_HANDLER
{
NSLog(@"Failed to write to '%@': %@", path, localException);
}
NS_ENDHANDLER
}
[writeHandle closeFile];
[task waitUntilExit];
if ([task terminationStatus] == 0)
{
didSucceed = YES;
}
else
{
NSLog(@"Graphing termination status %d", [task terminationStatus]);
}
LEAVE_POOL
return (didLaunch && didWrite && didSucceed) ? YES : NO;
}
@implementation AGSHtml
static NSMutableSet *textNodes = nil;
@ -177,18 +256,32 @@ static NSMutableSet *textNodes = nil;
{
NSString *s;
NSString *kind = (f == YES) ? @"rel=\"gsdoc\" href" : @"name";
s = [self makeURL: r ofType: t isRef: f];
if (s)
{
s = [NSString stringWithFormat: @"<a %@=\"%@\">", kind, s];
}
return s;
}
- (NSString*) makeURL: (NSString*)r
ofType: (NSString*)t
isRef: (BOOL)f
{
NSString *s;
NSString *hash = (f == YES) ? @"#" : @"";
if (f == NO || [localRefs globalRef: r type: t] != nil)
if (NO == f || [localRefs globalRef: r type: t] != nil)
{
s = [NSString stringWithFormat: @"<a %@=\"%@%@$%@\">",
kind, hash, t, r];
s = [NSString stringWithFormat: @"%@%@$%@",
hash, t, r];
}
else if ((s = [globalRefs globalRef: r type: t]) != nil)
{
s = [s stringByAppendingPathExtension: @"html"];
s = [NSString stringWithFormat: @"<a %@=\"%@%@%@$%@\">",
kind, s, hash, t, r];
s = [NSString stringWithFormat: @"%@%@%@$%@",
s, hash, t, r];
}
return [s stringByReplacingString: @":" withString: @"$"];
}
@ -353,6 +446,11 @@ static NSMutableSet *textNodes = nil;
dict = [refs objectForKey: type];
}
/* Put the index in a div with a class identifying its scope and type
* so that CSS can be used to style it.
*/
[buf appendFormat: @"<div class=\"%@_%@_index\">\n", scope, type];
if ([type isEqual: @"title"] == YES)
{
if ([dict count] > 1)
@ -588,6 +686,7 @@ static NSMutableSet *textNodes = nil;
}
[buf appendString: @"\n"];
}
[buf appendString: @"</div>\n"];
}
- (void) outputNode: (GSXMLNode*)node to: (NSMutableString*)buf
@ -686,19 +785,60 @@ static NSMutableSet *textNodes = nil;
classname = [prop objectForKey: @"name"];
unit = classname;
[buf appendString: indent];
[buf appendString: @"<h2>"];
[buf appendString: @"<h2 class=\"class\">"];
[buf appendString:
[self makeAnchor: classname ofType: @"class" name: classname]];
if (sup != nil)
if ([(sup = [sup stringByTrimmingSpaces]) length] == 0)
{
sup = [self typeRef: sup];
if (sup != nil)
sup = nil;
}
if (sup)
{
NSString *supref = [self typeRef: sup];
if (supref != nil)
{
[buf appendString: @" : "];
[buf appendString: sup];
[buf appendString: supref];
}
}
[buf appendString: @"</h2>\n"];
if (graphviz && sup)
{
NSString *url;
url = [self makeURL: sup ofType: @"class" isRef: YES];
if (url)
{
NSMutableString *dot = [NSMutableString string];
NSString *full;
NSString *svg;
[dot appendString: @"digraph inheritance {\n"];
[dot appendString: @" rankdir = \"TB\";\n"];
[dot appendString: @" node [margin=0 fontcolor=blue"
@" fontsize=24 width=0.5 shape=rectangle]\n"];
[dot appendFormat: @" {node [URL=\"%@\"] %@}\n", url, sup];
[dot appendString: @" ->\n"];
[dot appendFormat: @" {node [fontcolor=\"green\"] %@}\n",
classname];
[dot appendString: @"}"];
svg = [NSString stringWithFormat:
@"class_%@.svg", classname];
full = [[fileName stringByDeletingLastPathComponent]
stringByAppendingPathComponent: svg];
if (graph(full, dot, verbose))
{
[buf appendFormat: @"<img alt=\"%@\" src=\"%@\" />\n",
classname, svg];
}
}
}
[self outputUnit: node to: buf];
unit = nil;
classname = nil;
@ -2609,10 +2749,10 @@ static NSMutableSet *textNodes = nil;
*/
- (NSString*) typeRef: (NSString*)t
{
NSString *str = [t stringByTrimmingSpaces];
NSString *s;
unsigned end = [str length];
unsigned start;
NSString *str = [t stringByTrimmingSpaces];
NSString *s;
unsigned end = [str length];
unsigned start;
NSMutableString *ms = nil;
NSRange er;
NSRange sr;

View file

@ -113,6 +113,9 @@ HTMLLinker_OBJC_FILES = HTMLLinker.m
ifeq ($(OBJC_RUNTIME_LIB), ng)
AGSHtml.m_FILE_FLAGS+= -fobjc-arc
endif
ifneq ($(HAVE_DOT), )
AGSHtml.m_FILE_FLAGS+= -DHAVE_DOT=$(HAVE_DOT)
endif
# Reset this variable (defined in config.mak) to avoid useless linkage
# against the libraries gnustep-base uses.

View file

@ -13,6 +13,7 @@ DYNAMIC_LINKER=@DYNAMIC_LINKER@
HAVE_LIBXML=@HAVE_LIBXML@
HAVE_GNUTLS=@HAVE_GNUTLS@
HAVE_BLOCKS=@HAVE_BLOCKS@
HAVE_DOT=@HAVE_DOT@
WITH_FFI=@WITH_FFI@

134
configure vendored
View file

@ -658,6 +658,8 @@ GNUSTEP_GDOMAP_PORT_OVERRIDE
WARN_FLAGS
LDIR_FLAGS
INCLUDE_FLAGS
HAVE_DOT
DOT
USE_GMP
GS_HAVE_NSURLSESSION
HAVE_LIBCURL
@ -884,7 +886,8 @@ XML_LIBS
XSLT_CFLAGS
XSLT_LIBS
ICU_CFLAGS
ICU_LIBS'
ICU_LIBS
DOT'
# Initialize some variables set by options.
@ -1653,6 +1656,7 @@ Some influential environment variables:
XSLT_LIBS linker flags for XSLT, overriding pkg-config
ICU_CFLAGS C compiler flags for ICU, overriding pkg-config
ICU_LIBS linker flags for ICU, overriding pkg-config
DOT The name or full path of the graphviz dot command.
Use these variables to override the choices made by `configure' or to help
it to find libraries and programs with nonstandard names/locations.
@ -9355,82 +9359,6 @@ esac
#--------------------------------------------------------------------
# Following header checks needed for bzero in Storage.m and other places
#--------------------------------------------------------------------
# Autoupdate added the next two lines to ensure that your configure
# script's behavior did not change. They are probably safe to remove.
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
printf %s "checking for egrep... " >&6; }
if test ${ac_cv_path_EGREP+y}
then :
printf %s "(cached) " >&6
else $as_nop
if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
then ac_cv_path_EGREP="$GREP -E"
else
if test -z "$EGREP"; then
ac_path_EGREP_found=false
# Loop through the user's path and test for each of PROGNAME-LIST
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
do
IFS=$as_save_IFS
case $as_dir in #(((
'') as_dir=./ ;;
*/) ;;
*) as_dir=$as_dir/ ;;
esac
for ac_prog in egrep
do
for ac_exec_ext in '' $ac_executable_extensions; do
ac_path_EGREP="$as_dir$ac_prog$ac_exec_ext"
as_fn_executable_p "$ac_path_EGREP" || continue
# Check for GNU ac_path_EGREP and select it if it is found.
# Check for GNU $ac_path_EGREP
case `"$ac_path_EGREP" --version 2>&1` in
*GNU*)
ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
*)
ac_count=0
printf %s 0123456789 >"conftest.in"
while :
do
cat "conftest.in" "conftest.in" >"conftest.tmp"
mv "conftest.tmp" "conftest.in"
cp "conftest.in" "conftest.nl"
printf "%s\n" 'EGREP' >> "conftest.nl"
"$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
as_fn_arith $ac_count + 1 && ac_count=$as_val
if test $ac_count -gt ${ac_path_EGREP_max-0}; then
# Best one so far, save it but keep looking for a better one
ac_cv_path_EGREP="$ac_path_EGREP"
ac_path_EGREP_max=$ac_count
fi
# 10*(2^10) chars as input seems more than enough
test $ac_count -gt 10 && break
done
rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
esac
$ac_path_EGREP_found && break 3
done
done
done
IFS=$as_save_IFS
if test -z "$ac_cv_path_EGREP"; then
as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
fi
else
ac_cv_path_EGREP=$EGREP
fi
fi
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
printf "%s\n" "$ac_cv_path_EGREP" >&6; }
EGREP="$ac_cv_path_EGREP"
ac_fn_c_check_header_compile "$LINENO" "string.h" "ac_cv_header_string_h" "$ac_includes_default"
if test "x$ac_cv_header_string_h" = xyes
@ -14893,6 +14821,58 @@ fi
#--------------------------------------------------------------------
# Check dor 'dot', needed for graphs in autogsdoc output.
#--------------------------------------------------------------------
# Extract the first word of "dot", so it can be a program name with args.
set dummy dot; ac_word=$2
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
printf %s "checking for $ac_word... " >&6; }
if test ${ac_cv_path_DOT+y}
then :
printf %s "(cached) " >&6
else $as_nop
case $DOT in
[\\/]* | ?:[\\/]*)
ac_cv_path_DOT="$DOT" # Let the user override the test with a path.
;;
*)
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
case $as_dir in #(((
'') as_dir=./ ;;
*/) ;;
*) as_dir=$as_dir/ ;;
esac
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
ac_cv_path_DOT="$as_dir$ac_word$ac_exec_ext"
printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
;;
esac
fi
DOT=$ac_cv_path_DOT
if test -n "$DOT"; then
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $DOT" >&5
printf "%s\n" "$DOT" >&6; }
else
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
printf "%s\n" "no" >&6; }
fi
HAVE_DOT=$ac_cv_path_DOT
#--------------------------------------------------------------------
# Check whether nl_langinfo(CODESET) is supported, needed by Unicode.m.

View file

@ -73,7 +73,7 @@ builtin(include, config/addlibrarypath.m4)dnl
builtin(include, config/pkg.m4)dnl
AC_INIT
AC_PREREQ([2.60])
AC_PREREQ([2.71])
AC_CONFIG_SRCDIR([Source/NSArray.m])
# If GNUSTEP_MAKEFILES is undefined, try to use gnustep-config to determine it.
@ -2346,7 +2346,7 @@ AC_C_INLINE
#--------------------------------------------------------------------
# Following header checks needed for bzero in Storage.m and other places
#--------------------------------------------------------------------
AC_HEADER_STDC
AC_CHECK_HEADERS(string.h memory.h alloca.h)
#--------------------------------------------------------------------
@ -3838,6 +3838,13 @@ fi
AC_SUBST(USE_GMP)
#--------------------------------------------------------------------
# Check dor 'dot', needed for graphs in autogsdoc output.
#--------------------------------------------------------------------
AC_ARG_VAR([DOT], [The name or full path of the graphviz dot command.])
AC_PATH_PROG([DOT], [dot], [])
AC_SUBST(HAVE_DOT, [$ac_cv_path_DOT])
#--------------------------------------------------------------------
# Check whether nl_langinfo(CODESET) is supported, needed by Unicode.m.