typedoc2md / typedoc-plugin-markdown

A plugin for TypeDoc that enables TypeScript API documentation to be generated in Markdown.
https://typedoc-plugin-markdown.org
MIT License
693 stars 171 forks source link

Request: Single file from multiple file #59

Closed ozum closed 2 months ago

ozum commented 5 years ago

jsdoc2md optionally produce single markdown page from multiple modules/files by concatenating them. Also let developer use a handlebars template for that single page.

This is super useful to generate a README.md file for npm and github from code automatically.

Below is a simplified example handlebars template I use for that purpose:

<!-- DO NOT EDIT README.md (It will be overridden by README.hbs) -->

# {{changeCase "title" (package "name")}}

<!-- Badges from shield.io -->
> {{package "description"}}
>
> {{#each (package "shields")}}[![{{this.name}}][{{this.name}}]][{{this.name}}-url]{{/each}}

{{#each (package "shields")}}
[{{this.name}}]: {{this.image}}
[{{this.name}}-url]: {{this.url}}
{{/each}}

<!-- START doctoc -->
<!-- END doctoc -->

Some manual content I want to include in my README.

Below main part is added by jsdoc2md

# API
{{>main~}}

All those can be achieved programmatically with typedoc-plugin-markdown too, but it is helpful to have this feature built-in, because npm & github README.md file generation may be used by lots of people.

EDIT:

Why this is useful?

Sometimes, especially for small modules, it's enough to have a single README.md file for the whole documentation, and there is no need to have extra web sites/github pages or wiki pages. For example:

# Install

# Synopsis

# Usage

# API

jsdoc2md generated API comes here.
ozum commented 5 years ago

Today, I published concat-md for creating single file from multiple markdown files. Although it is not typedoc-plugin-markdown specific, I developed it primarily to use with typedoc-plugin-markdown.

It does not simply concat files, it adds necessary titles, and changes level of existing titles.

$ typedoc --plugin typedoc-plugin-markdown --mode file --out docs
$ npx concat-md --decrease-title-levels --dir-name-as-title docs > README.md

Above command;

This is an initial release, but I think it is OK to be used in projects.

@tgreyuk, I will appreciate if you can give it a try and provide some feedback.

tgreyuk commented 5 years ago

@ozum this looks pretty cool. sorry been a bit busy on some other stuff but I will test this out properly when i have a bit of time.

Vinnl commented 5 years ago

@ozum I'm loving my new autogenerated API documentation - thanks!

ozum commented 5 years ago

@tgreyuk, thanks. Also thanks to suggestion of @Vinnl, concat-md now converts links too.

ozum commented 5 years ago

@tgreyuk, I also appreciate if you mention md-concat in README with an example if you find it useful for typedoc-plugin-markdown.

SrBrahma commented 3 years ago

Any news about this? I really dislike writing READMEs, mainly because I JsDoc my code already and I hate to copy the stuff there to the README, and having to manually update the README is a pain for me.

I would like to just have a template README, write some specific texts, and everything else would be filled automagically, so I could just focus on what I like, that is coding.

Another current issue I am having, is the same as the https://github.com/tgreyuk/typedoc-plugin-markdown/issues/109. typedoc-plugin-markdown seems good, but the generated .md isn't really good to be used in a README as it takes too much space. My package is a React component, and I just want a table with the properties of the component. It's just ~10 props, but for me is already a mental pain having to copy/paste and having to maintain it through the time, specially because I have ~8 npm packages (not popular at all) and I plan to have many more, as I really like to do it.

I thought of creating from scratch basically another TypeDoc to do what I want using AST, but that would take more time that I am willing to spend on it. Seems to be a better option to use this package and do some transformations to achieve what I want. For now, I will write a quick .js to convert the generated .md to a table.

I already have a template generator for my projects that also creates a basic README. I just need that automatic README content filler so I can really automate my production line.

tgreyuk commented 3 years ago

@SrBrahma Thanks .. makes sense - I have been meaning to have a look at tabulating interface props for a while. Currently refactoring a few things which will make it easier to incorporate this. Will report back in due course.

SrBrahma commented 3 years ago

Thanks for the quick answer! I will as soon as I do my quick toTable.js post it here as it may help you or at least give you some ideas. I think I will just use regex to get the info I want from the .md.

SrBrahma commented 3 years ago

Done!! Probably it's just a dirty workaround that won't work on other cases that includes other JSDoc tags. In the entry file, differently from the original code typings, I made a prop required and other without the default tag, to handle and test different cases.

Entry example/test file, generated by typedoc-plugin-markdown ```md # Interface: ShadowI ## Properties ### startColor • **startColor**: `string` The color of the shadow when it's right next to the given content, leaving it. Accepts alpha channel. ___ ### finalColor • `Optional` **finalColor**: `string` The color of the shadow at the maximum distance from the content. **`default`** '#0000', transparent. ___ ### distance • `Optional` **distance**: `number` How far the shadow will go. ___ ### containerViewStyle • **containerViewStyle**: `StyleProp` The style of the view that contains the shadow and the children. **`default`** undefined ___ ### radius • `Optional` **radius**: `number` \| { `default?`: `number` ; `topLeft?`: `number` ; `topRight?`: `number` ; `bottomLeft?`: `number` ; `bottomRight?`: `number` } The radius of each corner of your child component. Passing a number will apply it to all corners. If passing an object, undefined corners will have the radius of the `default` property if it's defined, else 0. If undefined, as it's by default, and if getChildRadius, it will attemp to get the child radius style. Else, 0. **`default`** undefined ___ ### getChildRadius • `Optional` **getChildRadius**: `boolean` If it should try to get the radius from the child if `radius` prop is undefined. It will get the values for each corner, like `borderTopLeftRadius`, and also `borderRadius`. If a specific corner isn't defined, `borderRadius` value is used. If `borderRadius` isn't defined or < 0, 0 will be used. **`default`** true ___ ### sides • `Optional` **sides**: `Side`[] The sides of your content that will have the shadows drawn. Doesn't include corners. **`default`** ['left', 'right', 'top', 'bottom'] ___ ### corners • `Optional` **corners**: `Corner`[] The corners that will have the shadows drawn. **`default`** ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'] ___ ### offset • `Optional` **offset**: [x: string \| number, y: string \| nu
mber] Moves the shadow. Negative x moves it to the left, negative y moves it up. Accepts 'x%' values, in relation to the child's size. Read paintInside property description for related configuration. **`default`** [0, 0] ___ ### paintInside • `Optional` **paintInside**: `boolean` If the shadow should be applied inside the external shadows, below the child. You may want this as true when using offset or if your child have some transparency. **`default`** false ```
Converter TS file ```ts /* eslint-disable @typescript-eslint/prefer-regexp-exec */ import fs from 'fs'; const fileContent = fs.readFileSync('docs/interfaces/shadowi.md').toString(); // Match everything between ### and (___ or end of string) const matches = [...fileContent.matchAll(/###.+?(?=(___)|$)/sg)]; // s flag is . matches newline if (!matches.length) { throw new Error('No matches!'); } // let i = 0; const properties: { name?: string; type?: string; /** undefined means it's required. */ defaultVal?: string; description?: string; }[] = []; for (const match of matches) { // console.log(`Match #${i++}`); const content = match[0]; // console.log(content); const name = content.match(/### (.+)?/)?.[1]; const hasOptionalTag: boolean = !!content.match(/• `Optional`/); const defaultVal = content.match(/\*\*`default`\*\* (.+)/)?.[1] || (hasOptionalTag ? 'undefined' : undefined); const type = content.match(/• .+?: (.+)/)?.[1]; // It's after the type and before the (default or or EOS) const description = content.match(/•.+?\n([\s\S]+?)(?=(\*\*`default`\*\*)|$)/)?.[1].trim(); // console.log(name, defaultVal, hasOptionalTag, type); properties.push({ name, defaultVal, type, description }); // console.log(); } let resultString = `| Property | Type | Default | Description | --- | --- | --- | ---\n`; function ensureWrappingBackticks(value: string): string { return (value[0] === '`' ? value : `\`${value}\``); } // The type already includes wrapping backticks for (const prop of properties) { // Remove existing backticks (that sometimes are weirdly placed) and wrap it all with backticks const type = ensureWrappingBackticks((prop.type ?? '?').replace(/`/g, '')); const defaultVal = prop.defaultVal ? `${ensureWrappingBackticks(prop.defaultVal)}` : '**required**'; // Replace new lines with
tag const description = prop.description ? prop.description.replace(/\n/g, '
') : '-'; // '-' instead of a blank/undefined description // Ensuring backtick because my [x: string | number, y: string | number] wasn't backticked by some reason. resultString += `| **${prop.name}** | ${type} | ${defaultVal} | ${description}\n`; } fs.writeFileSync('result.md', resultString); ```
The result!! | Property | Type | Default | Description | --- | --- | --- | --- | **startColor** | `string` | **required** | The color of the shadow when it's right next to the given content, leaving it.
Accepts alpha channel. | **finalColor** | `string` | `'#0000', transparent.` | The color of the shadow at the maximum distance from the content. | **distance** | `number` | `undefined` | How far the shadow will go. | **containerViewStyle** | `StyleProp` | `undefined` | The style of the view that contains the shadow and the children. | **radius** | `number \| { default?: number ; topLeft?: number ; topRight?: number ; bottomLeft?: number ; bottomRight?: number }` | `undefined` | The radius of each corner of your child component. Passing a number will apply it to all corners.

If passing an object, undefined corners will have the radius of the `default` property if it's defined, else 0.

If undefined, as it's by default, and if getChildRadius, it will attemp to get the child radius style. Else, 0. | **getChildRadius** | `boolean` | `true` | If it should try to get the radius from the child if `radius` prop is undefined. It will get the values for each
corner, like `borderTopLeftRadius`, and also `borderRadius`. If a specific corner isn't defined, `borderRadius` value is used.
If `borderRadius` isn't defined or < 0, 0 will be used. | **sides** | `Side[]` | `['left', 'right', 'top', 'bottom']` | The sides of your content that will have the shadows drawn. Doesn't include corners. | **corners** | `Corner[]` | `['topLeft', 'topRight', 'bottomLeft', 'bottomRight']` | The corners that will have the shadows drawn. | **offset** | `[x: string \| number, y: string \| number]` | `[0, 0]` | Moves the shadow. Negative x moves it to the left, negative y moves it up.

Accepts 'x%' values, in relation to the child's size.

Read paintInside property description for related configuration. | **paintInside** | `boolean` | `false` | If the shadow should be applied inside the external shadows, below the child.

You may want this as true when using offset or if your child have some transparency.
SrBrahma commented 3 years ago
<!-- DO NOT EDIT README.md (It will be overridden by README.hbs) -->

# {{changeCase "title" (package "name")}}

<!-- Badges from shield.io -->
> {{package "description"}}
>
> {{#each (package "shields")}}[![{{this.name}}][{{this.name}}]][{{this.name}}-url]{{/each}}

{{#each (package "shields")}}
[{{this.name}}]: {{this.image}}
[{{this.name}}-url]: {{this.url}}
{{/each}}

<!-- START doctoc -->
<!-- END doctoc -->

Some manual content I want to include in my README.

Below main part is added by jsdoc2md

# API
{{>main~}}

@tgreyuk using that handlebars system, I made https://github.com/SrBrahma/react-native-shadow-2/tree/wip/resources/README. My handlebars only populates the {{shadowProperties}} table for now. Maybe we could make a typedoc2readme or typedoc2md? Writing for eg {{table interface@shadowi}} would get docs/interfaces/shadowi.md content, transform it into a table, and populate the corresponding field in .hbs. I don't know about jsdoc2md, maybe we could use its same syntax and maybe some of its parsers or do it as we like.

I am interested in doing it. In fact, my proof of concept is already done in that link, it would just require some further generalizations for a basic and useful release version. I don't think it would be possible a fully automatic README as it would probably get really messy, but using handlebars and typedocs automations would simplify and make the maintenance and the development of projects way easier.

Do you have any interest on it?