diff --git a/.docs/extra.js b/.docs/extra.js deleted file mode 100644 index 84d58939bb..0000000000 --- a/.docs/extra.js +++ /dev/null @@ -1,2 +0,0 @@ -// https://github.com/mkdocs/mkdocs/issues/803 -$(document).ready(() => $('.wy-nav-side').scrollTop($('li.toctree-l1.current').offset().top - $('.wy-nav-side').offset().top - 80)); diff --git a/.travis_commands.bash b/.travis_commands.bash index aa280c85b2..d706df2054 100644 --- a/.travis_commands.bash +++ b/.travis_commands.bash @@ -19,8 +19,8 @@ ponyc-build-packages(){ } ponyc-build-docs(){ - echo "Installing mkdocs..." - sudo -H pip install mkdocs + echo "Installing mkdocs and offical theme..." + sudo -H pip install mkdocs-ponylang echo "Building ponyc docs..." make CC="$CC1" CXX="$CXX1" docs-online diff --git a/src/common/paths.h b/src/common/paths.h index 2258808de6..73633ad6ee 100644 --- a/src/common/paths.h +++ b/src/common/paths.h @@ -42,4 +42,12 @@ PONY_DIRINFO* pony_dir_entry_next(PONY_DIR* dir); // Make the specified directory, including any intermediate directories void pony_mkdir(const char* path); +char* get_file_name(char* path); + +char* remove_ext(const char* path, char dot, char sep, size_t* allocated_size); + +bool get_compiler_exe_path(char* output_path); + +bool get_compiler_exe_directory(char* output_path); + #endif diff --git a/src/libponyc/pass/docgen.c b/src/libponyc/pass/docgen.c index fc4a0c5d3f..989cc1928e 100644 --- a/src/libponyc/pass/docgen.c +++ b/src/libponyc/pass/docgen.c @@ -6,7 +6,20 @@ #include "ponyassert.h" #include #include +#include "../common/paths.h" +// Type for storing the source code along the documentation. +typedef struct doc_sources_t +{ + const source_t* source; // The source file content + const char* filename; // The source filename. + size_t filename_alloc_size; // alloc size for the filename. + const char* doc_path; // The relative path of the generated source file. Used for putting correct path in mkdocs.yml + size_t doc_path_alloc_size; // alloc size for the doc_path. + const char* file_path; // The absolute path of the generate source file. + size_t file_path_alloc_size; // alloc size for the file_path. + struct doc_sources_t* next; // The next element of the linked list. +} doc_sources_t; // Define a type with the docgen state that needs to passed around the // functions below. @@ -35,9 +48,11 @@ typedef struct docgen_t FILE* type_file; const char* base_dir; const char* sub_dir; + char* doc_source_dir; size_t base_dir_buf_len; size_t sub_dir_buf_len; errors_t* errors; + doc_sources_t* included_sources; // As a linked list, being the first element or NULL } docgen_t; // Define options for doc generation @@ -46,7 +61,6 @@ typedef struct docgen_opt_t bool include_private; } docgen_opt_t; - // Define a list for keeping lists of ASTs ordered by name. // Each list should have an empty head node (prefereably on the stack). Other // nodes will be pool alloced, 1 node per AST item. @@ -59,7 +73,6 @@ typedef struct ast_list_t struct ast_list_t* next; } ast_list_t; - // Free the contents of the given list, but not the head node which must be // handled separately static void doc_list_free(ast_list_t* list) @@ -76,7 +89,6 @@ static void doc_list_free(ast_list_t* list) } } - // Add the given AST to the given list, under the specified name static void doc_list_add(ast_list_t* list, ast_t* ast, const char* name, bool sort) @@ -110,7 +122,6 @@ static void doc_list_add(ast_list_t* list, ast_t* ast, const char* name, prev->next = n; } - static bool is_for_testing(const char* name, ast_t* list) { pony_assert(name != NULL); @@ -150,7 +161,6 @@ static bool is_package_for_testing(const char* name) return (strcmp(name, "test") == 0 || strcmp(name, "builtin_test") == 0); } - // Add the given AST to the given list, using the name from the specified // child. // ASTs with hygenic names are ignored. @@ -187,7 +197,6 @@ static void doc_list_add_named(ast_list_t* list, ast_t* ast, size_t id_index, doc_list_add(list, ast, name, false); } - // Utilities // Cat together the given strings into a newly allocated buffer. @@ -229,7 +238,6 @@ static char* doc_cat(const char* a, const char* b, const char* c, return buffer; } - // Fully qualified type names (TQFNs). // We need unique names for types, for use in file names and links. The format // we use is: @@ -277,7 +285,6 @@ static char* write_tqfn(ast_t* type, const char* type_name, size_t* out_size) return buffer; } - // Open a file with the specified info. // The given filename extension should include a dot if one is needed. // The returned file handle must be fclosed() with no longer needed. @@ -308,7 +315,6 @@ static FILE* doc_open_file(docgen_t* docgen, bool in_sub_dir, return file; } - // Functions to handle types static void doc_type_list(docgen_t* docgen, docgen_opt_t* docgen_opt, ast_t* list, @@ -341,6 +347,20 @@ static const char* doc_get_cap(ast_t* cap) } } +static const doc_sources_t* get_doc_source(docgen_t* docgen, source_t* source) +{ + if (docgen->included_sources == NULL) + return NULL; + + const doc_sources_t* current_elem = docgen->included_sources; + + while (current_elem != NULL) { + if (source == current_elem->source) + return current_elem; + current_elem = current_elem->next; + } + return NULL; +} // Write the given type to the current type file static void doc_type(docgen_t* docgen, docgen_opt_t* docgen_opt, @@ -483,7 +503,26 @@ static void doc_type_list(docgen_t* docgen, docgen_opt_t* docgen_opt, ast_t* lis fprintf(docgen->type_file, "%s", postamble); } - +static void add_source_code_link(docgen_t* docgen, ast_t* elem) +{ + pony_assert(docgen != NULL); + pony_assert(docgen->type_file != NULL); + source_t* source = ast_source(elem); + const doc_sources_t* doc_source = NULL; + + if (source != NULL) + doc_source = get_doc_source(docgen, source); + + if (doc_source != NULL) { + fprintf( + docgen->type_file, + "\n[[Source]](%s#L%zd)\n", + doc_source->doc_path, ast_line(elem) + ); + } else { + fprintf(docgen->type_file, "\n\n"); + } +} // Functions to handle everything else @@ -500,7 +539,7 @@ static void doc_fields(docgen_t* docgen, docgen_opt_t* docgen_opt, if(fields->next == NULL) // No fields return; - + fprintf(docgen->type_file, "## %s\n\n", title); for(ast_list_t* p = fields->next; p != NULL; p = p->next) @@ -525,11 +564,11 @@ static void doc_fields(docgen_t* docgen, docgen_opt_t* docgen_opt, fprintf(docgen->type_file, "* %s %s: ", ftype, name); doc_type(docgen, docgen_opt, type, true, true); + add_source_code_link(docgen, field); fprintf(docgen->type_file, "\n\n---\n\n"); } } - // Write the given list of type parameters to the current type file, with // surrounding []. If the given list is empty nothing is written. static void doc_type_params(docgen_t* docgen, docgen_opt_t* docgen_opt, @@ -681,6 +720,9 @@ static void doc_method(docgen_t* docgen, docgen_opt_t* docgen_opt, // Method fprintf(docgen->type_file, "### %s", name); doc_type_params(docgen, docgen_opt, t_params, true, false); + + add_source_code_link(docgen, method); + fprintf(docgen->type_file, "\n\n"); // The docstring, if any @@ -734,7 +776,6 @@ static void doc_method(docgen_t* docgen, docgen_opt_t* docgen_opt, fprintf(docgen->type_file, "---\n\n"); } - // Write the given list of methods to the current type file. // The variety text is used as a heading. // If the list is empty nothing is written. @@ -755,6 +796,155 @@ static void doc_methods(docgen_t* docgen, docgen_opt_t* docgen_opt, doc_method(docgen, docgen_opt, p->ast); } +static char* concat(const char *s1, const char *s2, size_t* allocated_size) +{ + size_t str_size = strlen(s1) + strlen(s2) + 1; + char* result = (char*) ponyint_pool_alloc_size(str_size); //+1 for the null-terminator + *allocated_size = str_size; + strcpy(result, s1); + strcat(result, s2); + result[str_size - 1] = '\0'; + return result; +} + +static doc_sources_t* copy_source_to_doc_src(docgen_t* docgen, source_t* source, const char* package_name) +{ + pony_assert(docgen != NULL); + pony_assert(source != NULL); + pony_assert(package_name != NULL); + + doc_sources_t* result = (doc_sources_t*) ponyint_pool_alloc_size(sizeof(doc_sources_t)); + + char filename_copy[FILENAME_MAX]; + strcpy(filename_copy, source->file); + + const char* just_filename = get_file_name(filename_copy); + size_t filename_alloc_size = strlen(just_filename) + 1; + char* filename = (char*) ponyint_pool_alloc_size(filename_alloc_size); + strcpy(filename, just_filename); + size_t filename_without_ext_alloc_size = 0; + const char* filename_without_ext = remove_ext(filename, '.', 0, &filename_without_ext_alloc_size); + size_t filename_md_extension_alloc_size = 0; + const char* filename_md_extension = concat(filename_without_ext, ".md", &filename_md_extension_alloc_size); + + // Absolute path where a copy of the source will be put. + char source_dir[FILENAME_MAX]; + path_cat(docgen->doc_source_dir, package_name, source_dir); + + //Create directory for [documentationDir]/src/[package_name] + pony_mkdir(source_dir); + + // Get absolute path for [documentationDir]/src/[package_name]/[filename].md + size_t file_path_alloc_size = FILENAME_MAX; + char* path = (char*) ponyint_pool_alloc_size(FILENAME_MAX); + path_cat(source_dir, filename_md_extension, path); + + // Get relative path for [documentationDir]/src/[package_name]/ + // so it can be written in the mkdocs.yml file. + + size_t old_ptr_alloc_size = 0; + const char* doc_source_dir_relative = concat("src/", package_name, &old_ptr_alloc_size); + const char* old_ptr = doc_source_dir_relative; + size_t doc_source_dir_relative_alloc_size = 0; + doc_source_dir_relative = concat(doc_source_dir_relative, "/", &doc_source_dir_relative_alloc_size); + ponyint_pool_free_size(old_ptr_alloc_size, (void*) old_ptr); + + // Get relative path for [documentationDir]/src/[package_name]/[filename].md + size_t doc_path_alloc_size = 0; + const char* doc_path = concat(doc_source_dir_relative, filename_md_extension, &doc_path_alloc_size); + + ponyint_pool_free_size(filename_without_ext_alloc_size, (void*) filename_without_ext); + ponyint_pool_free_size(filename_md_extension_alloc_size, (void*) filename_md_extension); + ponyint_pool_free_size(doc_source_dir_relative_alloc_size, (void*) doc_source_dir_relative); + + // Section to copy source file to [documentationDir]/src/[package_name]/[filename].md + FILE* file = fopen(path, "w"); + + if (file != NULL) { + + // Escape markdown to tell this is Pony code + // Using multiple '```````' so hopefully the markdown parser + // will consider the whole text as a code block. + fprintf(file, "```````pony-full-source\n"); + fprintf(file, "%s", source->m); + fprintf(file, "\n```````"); + fclose(file); + + result->source = source; + + result->filename = filename; + result->filename_alloc_size= filename_alloc_size; + result->doc_path = doc_path; + result->doc_path_alloc_size = doc_path_alloc_size; + result->file_path = path; + result->file_path_alloc_size = file_path_alloc_size; + + result->next = NULL; + + return result; + } else { + ponyint_pool_free_size(filename_alloc_size, (void*) filename); + ponyint_pool_free_size(doc_path_alloc_size, (void*) doc_path); + ponyint_pool_free_size(file_path_alloc_size, (void*) path); + errorf(docgen->errors, NULL, "Could not write documentation to file %s", filename); + return NULL; + } +} + +static char* replace_path_separator(const char* path, size_t* name_len) { + size_t str_len = strlen(path); + *name_len = str_len + 1; + char* buffer = (char*) ponyint_pool_alloc_size(*name_len); + memcpy(buffer, path, str_len); + for(char* p = buffer; *p != '\0'; p++) + { + if(*p == '.') + *p = '_'; + if(*p == '/') + *p = '-'; +#ifdef PLATFORM_IS_WINDOWS + if(*p == '\\') + *p = '-'; +#endif + } + buffer[str_len] = '\0'; + return buffer; +} + +static void include_source_if_needed( + docgen_t* docgen, + source_t* source, + const char* package_name +) +{ + pony_assert(source != NULL); + pony_assert(docgen != NULL); + const char* source_path = source->file; + pony_assert(source_path != NULL); + + if (docgen->included_sources == NULL) { + docgen->included_sources = copy_source_to_doc_src(docgen, source, package_name); + } else { + doc_sources_t* current_source = docgen->included_sources; + doc_sources_t* last_valid_source = current_source; + bool is_already_included = false; + while (current_source != NULL && !is_already_included) { + pony_assert(current_source != NULL); + pony_assert(current_source->source != NULL); + pony_assert(current_source->source->file != NULL); + + if(strcmp(source_path, current_source->source->file) == 0) { + is_already_included = true; + } else { + last_valid_source = current_source; + current_source = current_source->next; + } + } + if (!is_already_included) { + last_valid_source->next = copy_source_to_doc_src(docgen, source, package_name); + } + } +} // Write a description of the given entity to its own type file. static void doc_entity(docgen_t* docgen, docgen_opt_t* docgen_opt, ast_t* ast) @@ -767,6 +957,26 @@ static void doc_entity(docgen_t* docgen, docgen_opt_t* docgen_opt, ast_t* ast) pony_assert(docgen->type_file == NULL); pony_assert(ast != NULL); + ast_t* package = ast; + while (ast_parent(package) != NULL && ast_id(package) != TK_PACKAGE) + { + package = ast_parent(package); + } + + size_t package_name_len; + + char* package_name = + replace_path_separator( + package_qualified_name(package), + &package_name_len + ); + + source_t* source = ast_source(ast); + if (source != NULL && package != NULL) + include_source_if_needed(docgen, source, package_name); + + ponyint_pool_free_size(package_name_len, package_name); + // First open a file size_t tqfn_len; char* tqfn = write_tqfn(ast, NULL, &tqfn_len); @@ -796,8 +1006,10 @@ static void doc_entity(docgen_t* docgen, docgen_opt_t* docgen_opt, ast_t* ast) // Now we can write the actual documentation for the entity fprintf(docgen->type_file, "# %s", name); + doc_type_params(docgen, docgen_opt, tparams, true, false); - fprintf(docgen->type_file, "\n\n"); + + add_source_code_link(docgen, ast); if(ast_id(doc) != TK_NONE) fprintf(docgen->type_file, "%s\n\n", ast_name(doc)); @@ -940,7 +1152,6 @@ static void doc_package_home(docgen_t* docgen, docgen->type_file = NULL; } - // Document the given package static void doc_package(docgen_t* docgen, docgen_opt_t* docgen_opt, ast_t* ast) { @@ -1047,7 +1258,6 @@ static void doc_packages(docgen_t* docgen, docgen_opt_t* docgen_opt, } } - // Delete all the files in the specified directory static void doc_rm_star(const char* path) { @@ -1083,7 +1293,6 @@ static void doc_rm_star(const char* path) pony_closedir(dir); } - /* Ensure that the directories we need exist and are empty. * * Our base directory has the name: @@ -1124,7 +1333,6 @@ static void doc_setup_dirs(docgen_t* docgen, ast_t* program, pass_opt_t* opt) doc_rm_star(docgen->sub_dir); } - void generate_docs(ast_t* program, pass_opt_t* options) { pony_assert(program != NULL); @@ -1144,6 +1352,11 @@ void generate_docs(ast_t* program, pass_opt_t* options) docgen.index_file = doc_open_file(&docgen, false, "mkdocs", ".yml"); docgen.home_file = doc_open_file(&docgen, true, "index", ".md"); docgen.type_file = NULL; + docgen.included_sources = NULL; + + docgen.doc_source_dir = (char*) ponyint_pool_alloc_size(FILENAME_MAX); + path_cat(docgen.base_dir, "docs/src", docgen.doc_source_dir); + pony_mkdir(docgen.doc_source_dir); // Write documentation files if(docgen.index_file != NULL && docgen.home_file != NULL) @@ -1154,13 +1367,27 @@ void generate_docs(ast_t* program, pass_opt_t* options) fprintf(docgen.home_file, "Packages\n\n"); fprintf(docgen.index_file, "site_name: %s\n", name); - fprintf(docgen.index_file, "theme: readthedocs\n"); + fprintf(docgen.index_file, "theme: ponylang\n"); fprintf(docgen.index_file, "pages:\n"); fprintf(docgen.index_file, "- %s: index.md\n", name); doc_packages(&docgen, &docgen_opt, program); } + if (docgen.included_sources != NULL) { + fprintf(docgen.index_file, "- source:\n"); + doc_sources_t* current_source = docgen.included_sources; + while (current_source != NULL) { + fprintf(docgen.index_file, " - %s : \"%s\" \n" , current_source->filename, current_source->doc_path); + ponyint_pool_free_size(current_source->filename_alloc_size, (void*) current_source->filename); + ponyint_pool_free_size(current_source->doc_path_alloc_size, (void*) current_source->doc_path); + ponyint_pool_free_size(current_source->file_path_alloc_size, (void*) current_source->file_path); + doc_sources_t* current_source_ptr_copy = current_source; + current_source = current_source->next; + ponyint_pool_free_size(sizeof(doc_sources_t), (void*) current_source_ptr_copy); + } + } + // Tidy up if(docgen.index_file != NULL) fclose(docgen.index_file); diff --git a/src/libponyc/pkg/package.c b/src/libponyc/pkg/package.c index c65da96bc7..1fb03d109c 100644 --- a/src/libponyc/pkg/package.c +++ b/src/libponyc/pkg/package.c @@ -20,16 +20,6 @@ #include #include -#if defined(PLATFORM_IS_LINUX) -#include -#elif defined(PLATFORM_IS_MACOSX) -#include -#elif defined(PLATFORM_IS_BSD) -#include -#include -#include -#endif - #define EXTENSION ".pony" @@ -676,61 +666,15 @@ static bool add_safe(const char* path, pass_opt_t* opt) static bool add_exec_dir(pass_opt_t* opt) { char path[FILENAME_MAX]; - bool success; + bool success = get_compiler_exe_directory(path); errors_t* errors = opt->check.errors; -#ifdef PLATFORM_IS_WINDOWS - // Specified size *includes* nul terminator - GetModuleFileName(NULL, path, FILENAME_MAX); - success = (GetLastError() == ERROR_SUCCESS); -#elif defined PLATFORM_IS_LINUX - // Specified size *excludes* nul terminator - ssize_t r = readlink("/proc/self/exe", path, FILENAME_MAX - 1); - success = (r >= 0); - - if(success) - path[r] = '\0'; -#elif defined PLATFORM_IS_BSD - int mib[4]; - mib[0] = CTL_KERN; - mib[1] = KERN_PROC; - mib[2] = KERN_PROC_PATHNAME; - mib[3] = -1; - - size_t len = FILENAME_MAX; - int r = sysctl(mib, 4, path, &len, NULL, 0); - success = (r == 0); -#elif defined PLATFORM_IS_MACOSX - char exec_path[FILENAME_MAX]; - uint32_t size = sizeof(exec_path); - int r = _NSGetExecutablePath(exec_path, &size); - success = (r == 0); - - if(success) - { - pony_realpath(exec_path, path); - } -#else -# error Unsupported platform for exec_path() -#endif - if(!success) { - errorf(errors, NULL, "Error determining executable path"); - return false; - } - - // We only need the directory - char *p = strrchr(path, PATH_SLASH); - - if(p == NULL) - { - errorf(errors, NULL, "Error determining executable path (%s)", path); + errorf(errors, NULL, "Error determining executable path or directory."); return false; } - p++; - *p = '\0'; add_path(path, opt); // Allow ponyc to find the lib directory when it is installed. diff --git a/src/libponyc/platform/paths.c b/src/libponyc/platform/paths.c index a32aede888..fce1b8cdce 100644 --- a/src/libponyc/platform/paths.c +++ b/src/libponyc/platform/paths.c @@ -1,6 +1,23 @@ #include #include "../../libponyrt/mem/pool.h" #include +#include + +#if defined(PLATFORM_IS_LINUX) +#include +#elif defined(PLATFORM_IS_MACOSX) +#include +#elif defined(PLATFORM_IS_BSD) +#include +#include +#include +#endif + +#ifdef PLATFORM_IS_WINDOWS +# define PATH_SLASH '\\' +#else +# define PATH_SLASH '/' +#endif PONY_DIR* pony_opendir(const char* path, PONY_ERRNO* err) { @@ -132,3 +149,123 @@ void pony_mkdir(const char* path) ponyint_pool_free_size(path_len + 1, buf); } + +#ifdef PLATFORM_IS_WINDOWS +# include +# pragma comment(lib, "shlwapi.lib") +#else +# include +#endif + + +char* get_file_name(char* filename) +{ +#ifdef PLATFORM_IS_WINDOWS + PathStripPath((LPSTR) filename); + return filename; +#else + return basename(filename); +#endif +} + +// https://stackoverflow.com/questions/2736753/how-to-remove-extension-from-file-name +// remove_ext: removes the "extension" from a file spec. +// path is the string to process. +// dot is the extension separator. +// sep is the path separator (0 means to ignore). +// Returns an allocated string identical to the original but +// with the extension removed. It must be freed when you're +// finished with it. +// If you pass in NULL or the new string can't be allocated, +// it returns NULL. +char* remove_ext(const char* path, char dot, char sep, size_t* allocated_size) +{ + char *retstr, *lastdot, *lastsep; + // Error checks and allocate string. + if (path == NULL) + return NULL; + + *allocated_size = strlen(path) + 1; + + retstr = (char*) ponyint_pool_alloc_size(*allocated_size); + + // Make a copy and find the relevant characters. + strcpy(retstr, path); + lastdot = strrchr(retstr, dot); + lastsep = (sep == 0) ? NULL : strrchr(retstr, sep); + + // If it has an extension separator. + if (lastdot != NULL) { + // and it's before the extension separator. + if (lastsep != NULL) { + if (lastsep < lastdot) { + // then remove it. + *lastdot = '\0'; + } + } + else { + // Has extension separator with no path separator. + *lastdot = '\0'; + } + } + // Return the modified string. + return retstr; +} + +bool get_compiler_exe_path(char* output_path) +{ + bool success = false; + #ifdef PLATFORM_IS_WINDOWS + // Specified size *includes* nul terminator + GetModuleFileName(NULL, output_path, FILENAME_MAX); + success = (GetLastError() == ERROR_SUCCESS); +#elif defined PLATFORM_IS_LINUX + // Specified size *excludes* nul terminator + ssize_t r = readlink("/proc/self/exe", output_path, FILENAME_MAX - 1); + success = (r >= 0); + + if(success) + output_path[r] = '\0'; +#elif defined PLATFORM_IS_BSD + int mib[4]; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; + + size_t len = FILENAME_MAX; + int r = sysctl(mib, 4, output_path, &len, NULL, 0); + success = (r == 0); +#elif defined PLATFORM_IS_MACOSX + char exec_path[FILENAME_MAX]; + uint32_t size = sizeof(exec_path); + int r = _NSGetExecutablePath(exec_path, &size); + success = (r == 0); + + if(success) + { + pony_realpath(exec_path, output_path); + } +#else +# error Unsupported platform for exec_path() +#endif + return success; +} + +bool get_compiler_exe_directory(char* output_path) +{ + bool can_get_compiler_path = get_compiler_exe_path(output_path); + if (can_get_compiler_path) + { + char *p = strrchr(output_path, PATH_SLASH); + if(p == NULL) + { + return false; + } + p++; + *p = '\0'; + return true; + } else { + return false; + } +} \ No newline at end of file