readme massaging
made processors chainable move readme massging code out of util made github heuristic a special case fixes #17
This commit is contained in:
parent
9faf610e38
commit
ee533d1e69
12 changed files with 299 additions and 384 deletions
|
@ -19,7 +19,7 @@ module.exports = function (argv: string[]): void {
|
|||
.description('Packages an extension')
|
||||
.option('-o, --out [path]', 'Location of the package')
|
||||
.option('--baseContentUri [uri]', 'Base absolute URI that all relative URIs in the readme will get transformed as')
|
||||
.action(({ out, baseContentUri }) => catchFatal(packageCommand(out, baseContentUri)));
|
||||
.action(({ out, baseContentUri }) => catchFatal(packageCommand({ packagePath: out, baseContentUri })));
|
||||
|
||||
program
|
||||
.command('publish')
|
||||
|
|
165
src/package.ts
165
src/package.ts
|
@ -9,8 +9,14 @@ import * as _glob from 'glob';
|
|||
import * as minimatch from 'minimatch';
|
||||
import * as denodeify from 'denodeify';
|
||||
import * as mime from 'mime';
|
||||
import * as urljoin from 'url-join';
|
||||
|
||||
const readFile = denodeify<string, string, string>(fs.readFile);
|
||||
interface IReadFile {
|
||||
(filePath: string): Promise<Buffer>;
|
||||
(filePath: string, encoding?: string): Promise<string>;
|
||||
}
|
||||
|
||||
const readFile: IReadFile = <any> denodeify(fs.readFile);
|
||||
const unlink = denodeify<string, void>(fs.unlink);
|
||||
const exec = denodeify<string, { cwd?: string; }, { stdout: string; stderr: string; }>(cp.exec, (err, stdout, stderr) => [err, { stdout, stderr }]);
|
||||
const glob = denodeify<string, _glob.IOptions, string[]>(_glob);
|
||||
|
@ -27,6 +33,14 @@ export interface IFile {
|
|||
localPath?: string;
|
||||
}
|
||||
|
||||
export function read(file: IFile): Promise<Buffer> {
|
||||
if (file.contents) {
|
||||
return Promise.resolve(file.contents);
|
||||
} else {
|
||||
return readFile(file.localPath);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IPackageResult {
|
||||
manifest: Manifest;
|
||||
packagePath: string;
|
||||
|
@ -37,17 +51,23 @@ export interface IAsset {
|
|||
path: string;
|
||||
}
|
||||
|
||||
interface IProcessor {
|
||||
onFile(file: IFile): void;
|
||||
export interface IPackageOptions {
|
||||
cwd?: string;
|
||||
packagePath?: string;
|
||||
baseContentUri?: string;
|
||||
}
|
||||
|
||||
export interface IProcessor {
|
||||
onFile(file: IFile): Promise<IFile>;
|
||||
assets: IAsset[];
|
||||
vsix: any;
|
||||
}
|
||||
|
||||
abstract class BaseProcessor implements IProcessor {
|
||||
export abstract class BaseProcessor implements IProcessor {
|
||||
constructor(protected manifest: Manifest) {}
|
||||
public assets: IAsset[] = [];
|
||||
public vsix: any = Object.create(null);
|
||||
onFile(file: IFile): void {}
|
||||
abstract onFile(file: IFile): Promise<IFile>;
|
||||
}
|
||||
|
||||
class MainProcessor extends BaseProcessor {
|
||||
|
@ -64,15 +84,73 @@ class MainProcessor extends BaseProcessor {
|
|||
links: { repository: manifest.repository }
|
||||
});
|
||||
}
|
||||
onFile(file: IFile): Promise<IFile> {
|
||||
return Promise.resolve(file);
|
||||
}
|
||||
}
|
||||
|
||||
const README_REGEX = /^extension\/README.md$/i
|
||||
|
||||
class ReadmeProcessor extends BaseProcessor {
|
||||
onFile(file: IFile): void {
|
||||
const normalizedPath = util.normalize(file.path);
|
||||
if (README_REGEX.test(normalizedPath)) {
|
||||
this.assets.push({ type: 'Microsoft.VisualStudio.Services.Content.Details', path: normalizedPath });
|
||||
export class ReadmeProcessor extends BaseProcessor {
|
||||
|
||||
private baseContentUri: string;
|
||||
|
||||
constructor(manifest: Manifest, options: IPackageOptions= {}) {
|
||||
super(manifest);
|
||||
this.baseContentUri = options.baseContentUri || this.guessBaseContentUri();
|
||||
}
|
||||
|
||||
onFile(file: IFile): Promise<IFile> {
|
||||
const path = util.normalize(file.path);
|
||||
|
||||
if (/^extension\/readme.md$/i.test(path)) {
|
||||
this.assets.push({ type: 'Microsoft.VisualStudio.Services.Content.Details', path });
|
||||
|
||||
if (this.baseContentUri) {
|
||||
return read(file)
|
||||
.then(buffer => buffer.toString('utf8'))
|
||||
.then(contents => contents.replace(/\[([^\[]+)\]\(([^\)]+)\)/g, (all, title, link) =>
|
||||
all.substr(0, title.length) + all.substr(title.length).replace(link, this.prependBaseContentUri(link))
|
||||
))
|
||||
.then(contents => ({
|
||||
path: file.path,
|
||||
contents: new Buffer(contents)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(file);
|
||||
}
|
||||
|
||||
private prependBaseContentUri(link: string): string {
|
||||
if (/^(?:\w+:)\/\//.test(link)) {
|
||||
return link;
|
||||
}
|
||||
|
||||
if (link[0] === '#') {
|
||||
return link;
|
||||
}
|
||||
|
||||
return urljoin(this.baseContentUri, link);
|
||||
}
|
||||
|
||||
// GitHub heuristics
|
||||
private guessBaseContentUri(): string {
|
||||
let repository = null;
|
||||
|
||||
if (typeof this.manifest.repository === 'string') {
|
||||
repository = this.manifest.repository;
|
||||
} else if (this.manifest.repository && typeof this.manifest.repository['url'] === 'string') {
|
||||
repository = this.manifest.repository['url'];
|
||||
}
|
||||
|
||||
if (!repository) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const regex = /github\.com\/([^/]+)\/([^/]+)(\/|$)/;
|
||||
const match = regex.exec(repository);
|
||||
|
||||
if (match) {
|
||||
return `https://raw.githubusercontent.com/${ match[1] }/${ match[2] }/master`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,12 +174,13 @@ class LicenseProcessor extends BaseProcessor {
|
|||
this.vsix.license = null;
|
||||
}
|
||||
|
||||
onFile(file: IFile): void {
|
||||
onFile(file: IFile): Promise<IFile> {
|
||||
const normalizedPath = util.normalize(file.path);
|
||||
if (this.filter(normalizedPath)) {
|
||||
this.assets.push({ type: 'Microsoft.VisualStudio.Services.Content.License', path: normalizedPath });
|
||||
this.vsix.license = normalizedPath;
|
||||
}
|
||||
return Promise.resolve(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,12 +195,13 @@ class IconProcessor extends BaseProcessor {
|
|||
this.vsix.icon = null;
|
||||
}
|
||||
|
||||
onFile(file: IFile): void {
|
||||
onFile(file: IFile): Promise<IFile> {
|
||||
const normalizedPath = util.normalize(file.path);
|
||||
if (normalizedPath === this.icon) {
|
||||
this.assets.push({ type: 'Microsoft.VisualStudio.Services.Icons.Default', path: normalizedPath });
|
||||
this.vsix.icon = this.icon;
|
||||
}
|
||||
return Promise.resolve(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,22 +242,23 @@ export function readManifest(cwd: string): Promise<Manifest> {
|
|||
});
|
||||
}
|
||||
|
||||
export function toVsixManifest(manifest: Manifest, files: IFile[]): Promise<string> {
|
||||
export function toVsixManifest(manifest: Manifest, files: IFile[], options: IPackageOptions = {}): Promise<string> {
|
||||
const processors: IProcessor[] = [
|
||||
new MainProcessor(manifest),
|
||||
new ReadmeProcessor(manifest),
|
||||
new ReadmeProcessor(manifest, options),
|
||||
new LicenseProcessor(manifest),
|
||||
new IconProcessor(manifest)
|
||||
];
|
||||
|
||||
files.forEach(f => processors.forEach(p => p.onFile(f)));
|
||||
|
||||
const assets = _.flatten(processors.map(p => p.assets));
|
||||
const vsix = (<any> _.assign)({ assets }, ...processors.map(p => p.vsix));
|
||||
|
||||
return readFile(vsixManifestTemplatePath, 'utf8')
|
||||
.then(vsixManifestTemplateStr => _.template(vsixManifestTemplateStr))
|
||||
.then(vsixManifestTemplate => vsixManifestTemplate(vsix));
|
||||
return Promise.all(files.map(file => util.chain(file, processors, (file, processor) => processor.onFile(file))))
|
||||
.then(files => {
|
||||
const assets = _.flatten(processors.map(p => p.assets));
|
||||
const vsix = (<any> _.assign)({ assets }, ...processors.map(p => p.vsix));
|
||||
|
||||
return readFile(vsixManifestTemplatePath, 'utf8')
|
||||
.then(vsixManifestTemplateStr => _.template(vsixManifestTemplateStr))
|
||||
.then(vsixManifestTemplate => vsixManifestTemplate(vsix));
|
||||
});
|
||||
}
|
||||
|
||||
export function toContentTypes(files: IFile[]): Promise<string> {
|
||||
|
@ -218,30 +299,14 @@ function collectFiles(cwd: string, manifest: Manifest): Promise<string[]> {
|
|||
});
|
||||
}
|
||||
|
||||
function getUrlPrefix(manifest: Manifest): string {
|
||||
let repository = null;
|
||||
if (typeof manifest.repository === 'string') {
|
||||
repository = manifest.repository;
|
||||
}
|
||||
if (manifest.repository && manifest.repository['url']) {
|
||||
repository = manifest.repository['url'];
|
||||
}
|
||||
|
||||
return repository ? `${ repository }/blob/master` : '';
|
||||
}
|
||||
|
||||
export function collect(cwd: string, manifest: Manifest, baseContentUri = null): Promise<IFile[]> {
|
||||
export function collect(manifest: Manifest, options: IPackageOptions = {}): Promise<IFile[]> {
|
||||
const cwd = options.cwd || process.cwd();
|
||||
|
||||
return collectFiles(cwd, manifest).then(fileNames => {
|
||||
const files:IFile[] = fileNames.map(f => ({ path: `extension/${ f }`, localPath: path.join(cwd, f) }));
|
||||
const readme = files.filter(f => README_REGEX.test(util.normalize(f.path)))[0];
|
||||
const prefix = baseContentUri ? baseContentUri : getUrlPrefix(manifest);
|
||||
|
||||
return Promise.all([toVsixManifest(manifest, files), toContentTypes(files), readme ? util.massageMarkdownLinks(readme.localPath, prefix) : ''])
|
||||
return Promise.all([toVsixManifest(manifest, files, options), toContentTypes(files)])
|
||||
.then(result => {
|
||||
if (readme) {
|
||||
readme.contents = new Buffer(result[2], 'utf8');
|
||||
}
|
||||
|
||||
return [
|
||||
{ path: 'extension.vsixmanifest', contents: new Buffer(result[0], 'utf8') },
|
||||
{ path: '[Content_Types].xml', contents: new Buffer(result[1], 'utf8') },
|
||||
|
@ -288,16 +353,18 @@ function prepublish(cwd: string, manifest: Manifest): Promise<Manifest> {
|
|||
.catch(err => Promise.reject(err.message));
|
||||
}
|
||||
|
||||
export function pack(packagePath: string = null, baseContentUri: string = null, cwd = process.cwd()): Promise<IPackageResult> {
|
||||
export function pack(options: IPackageOptions = {}): Promise<IPackageResult> {
|
||||
const cwd = options.cwd || process.cwd();
|
||||
|
||||
return readManifest(cwd)
|
||||
.then(manifest => prepublish(cwd, manifest))
|
||||
.then(manifest => collect(cwd, manifest, baseContentUri)
|
||||
.then(files => writeVsix(files, path.resolve(packagePath || defaultPackagePath(cwd, manifest)))
|
||||
.then(manifest => collect(manifest, options)
|
||||
.then(files => writeVsix(files, path.resolve(options.packagePath || defaultPackagePath(cwd, manifest)))
|
||||
.then(packagePath => ({ manifest, packagePath }))));
|
||||
}
|
||||
|
||||
export function packageCommand(packagePath: string = null, baseContentUri: string = null, cwd = process.cwd()): Promise<any> {
|
||||
return pack(packagePath, baseContentUri, cwd)
|
||||
export function packageCommand(options: IPackageOptions = {}): Promise<any> {
|
||||
return pack(options)
|
||||
.then(({ packagePath }) => console.log(`Created: ${ packagePath }`));
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ const galleryUrl = 'https://app.market.visualstudio.com';
|
|||
|
||||
export function publish(cwd = process.cwd()): Promise<any> {
|
||||
return tmpName()
|
||||
.then(packagePath => pack(packagePath, cwd))
|
||||
.then(packagePath => pack({ packagePath, cwd }))
|
||||
.then(result => {
|
||||
const { manifest, packagePath } = result;
|
||||
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
# VSCode Extension Manager
|
||||
|
||||
This tool assists in publishing Visual Studio Code extensions.
|
||||
|
||||
[**Documentation**](https://github.com/Microsoft/vscode-extensionbuilders/blob/master/docs/tools/vscecli.md)
|
||||
|
||||
## Usage
|
||||
|
||||
First, install using npm:
|
||||
|
||||
```
|
||||
npm install -g vsce
|
||||
```
|
||||
|
||||
Then, `cd` to your extension's directory.
|
||||
It is good practice to list the files that will be included in your extension's
|
||||
package, before you actually publish:
|
||||
|
||||
```
|
||||
$ vsce ls
|
||||
hello.js
|
||||
package.json
|
||||
```
|
||||
|
||||
Publish away:
|
||||
|
||||
```
|
||||
$ vsce publish
|
||||
Publishing uuid@0.0.1...
|
||||
Successfully published uuid@0.0.1!
|
||||
```
|
|
@ -1,121 +0,0 @@
|
|||
# README
|
||||
|
||||
>**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file.
|
||||
|
||||
This README covers off:
|
||||
* [Functionality](#functionality)
|
||||
* [Install](#install)
|
||||
* [Run and Configure](#run-and-configure)
|
||||
* [Known Issues/Bugs](#known-issuesbugs)
|
||||
* [Backlog](#backlog)
|
||||
* [How to Debug](#how-to-debug)
|
||||
|
||||
# Functionality
|
||||
|
||||
Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document.
|
||||
|
||||
![Underscores and hovers](https://github.com/Microsoft/vscode-SpellMD/raw/master/images/SpellMDDemo1.gif)
|
||||
|
||||
The status bar lets you quickly navigate to any issue and you can see all positions in the gutter.
|
||||
|
||||
![Jump to issues](https://github.com/Microsoft/vscode-SpellMD/raw/master/images/SpellMDDemo2.gif)
|
||||
|
||||
The `spellMD.json` config file is watched so you can add more ignores or change mappings at will.
|
||||
|
||||
![Add to dictionary](https://github.com/Microsoft/vscode-SpellMD/raw/master/images/SpellMDDemo3.gif)
|
||||
|
||||
![issue](https://github.com/Microsoft/vscode-SpellMD/raw/master/issue)
|
||||
|
||||
# Install
|
||||
This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions.
|
||||
|
||||
|
||||
To clone the extension and load locally...
|
||||
|
||||
```
|
||||
git clone https://github.com/Microsoft/vscode-SpellMD.git
|
||||
npm install
|
||||
tsc
|
||||
```
|
||||
|
||||
>**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`.
|
||||
|
||||
Copy the extension folder into user settings.
|
||||
|
||||
Depending on your platform, this folder is located here:
|
||||
* **Windows** `%USERPROFILE%\.vscode\extensions`
|
||||
* **Mac** `$HOME/.vscode/extensions`
|
||||
* **Linux** `$HOME/.vscode/extensions`
|
||||
|
||||
# Run and Configure
|
||||
|
||||
## Enable via Config setting
|
||||
Add the following setting to your WorkSpace [or User] settings:
|
||||
|
||||
```json
|
||||
"spellMD.enable": true,
|
||||
```
|
||||
|
||||
## Open a Markdown file
|
||||
Then open any Markdown file and BOOM.
|
||||
|
||||
## Configure
|
||||
The plug-in supports and watches a config file. This should go in the `.vscode` directory and needs to be called `spellMD.json`. This file has the following sections:
|
||||
* **version** incase I change the format
|
||||
* **ignoreWordsList** an array of strings that represents words not to check
|
||||
* **mistakeTypeToStatus** we detect many error types and this is how they map to VS Code severities
|
||||
* **replaceRegExp** this is an arry of RegExps represented as strings for pre-parsing the doc e.g. removing code blocks
|
||||
|
||||
> **Tip:** you need to convert any `\` from the RegExp to a `\\\\` sequence for the JSON to parse.
|
||||
|
||||
Here is an example file...
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"ignoreWordsList": [
|
||||
"IntelliSense", "project.json", "nodejs", "transpiled", "ASPNET"
|
||||
],
|
||||
"mistakeTypeToStatus": {
|
||||
"Passive voice": "Info",
|
||||
"Spelling": "Error",
|
||||
"Complex Expression": "Info",
|
||||
"Hidden Verbs": "Info",
|
||||
"Hyphen Required": "Error",
|
||||
"Did you mean...": "Info",
|
||||
"Repeated Word": "Error",
|
||||
"Missing apostrophe": "Error",
|
||||
"Redundant Expression": "Info",
|
||||
"Cliches": "Warn",
|
||||
"Missing Word": "Warn",
|
||||
"Make I uppercase": "Error"
|
||||
},
|
||||
"replaceRegExp": [
|
||||
"/^((`{3}\\\\s*)(\\\\w+)?(\\\\s*([\\\\w\\\\W]+?)\\\\n*)\\\\2)\\\\n*(?:[^\\\\S\\\\w\\\\s]|$)/gm",
|
||||
"/\\\\]\\\\(([^\\\\)]+)\\\\)/g"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
# Backlog
|
||||
|
||||
Here are some ideas - fell free to add more.
|
||||
|
||||
1. Let the user act on the suggestions e.g. a right-click or `Alt+S` opens command palette with suggestions, selection replaces.
|
||||
2. Include the Text Tools extension w/ this i.e. WordCount, HTMLEncode etc
|
||||
3. On project open check every file in the background
|
||||
1. Have an `excludeFilesList` in the options
|
||||
2. Suppress some types of issue completely i.e. don't report `Cliches` less noise in the list
|
||||
4. Provide an action to add a word to the dictionary e.g. `Alt+A`
|
||||
1. Automatically create a spellMD.json file when a user adds a word
|
||||
2. When adding a word also add plurals/sentence case etc
|
||||
|
||||
|
||||
# Debug This Code
|
||||
Run this command in the directory w/ markdown files to check.
|
||||
|
||||
```
|
||||
code --debugLanguageWorker=* --extensionDevelopmentPath="c:\src\vscode-SpellMD" .
|
||||
```
|
||||
|
||||
Then open `VS Code` in the project directory and `Attach` to the running process.
|
|
@ -1,31 +0,0 @@
|
|||
# VSCode Extension Manager
|
||||
|
||||
This tool assists in publishing Visual Studio Code extensions.
|
||||
|
||||
[**Documentation**](vscode-extensionbuilders/blob/master/docs/tools/vscecli.md)
|
||||
|
||||
## Usage
|
||||
|
||||
First, install using npm:
|
||||
|
||||
```
|
||||
npm install -g vsce
|
||||
```
|
||||
|
||||
Then, `cd` to your extension's directory.
|
||||
It is good practice to list the files that will be included in your extension's
|
||||
package, before you actually publish:
|
||||
|
||||
```
|
||||
$ vsce ls
|
||||
hello.js
|
||||
package.json
|
||||
```
|
||||
|
||||
Publish away:
|
||||
|
||||
```
|
||||
$ vsce publish
|
||||
Publishing uuid@0.0.1...
|
||||
Successfully published uuid@0.0.1!
|
||||
```
|
|
@ -1,121 +0,0 @@
|
|||
# README
|
||||
|
||||
>**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file.
|
||||
|
||||
This README covers off:
|
||||
* [Functionality](#functionality)
|
||||
* [Install](#install)
|
||||
* [Run and Configure](#run-and-configure)
|
||||
* [Known Issues/Bugs](#known-issuesbugs)
|
||||
* [Backlog](#backlog)
|
||||
* [How to Debug](#how-to-debug)
|
||||
|
||||
# Functionality
|
||||
|
||||
Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document.
|
||||
|
||||
![Underscores and hovers](/images/SpellMDDemo1.gif)
|
||||
|
||||
The status bar lets you quickly navigate to any issue and you can see all positions in the gutter.
|
||||
|
||||
![Jump to issues](/images/SpellMDDemo2.gif)
|
||||
|
||||
The `spellMD.json` config file is watched so you can add more ignores or change mappings at will.
|
||||
|
||||
![Add to dictionary](/images/SpellMDDemo3.gif)
|
||||
|
||||
![issue](issue)
|
||||
|
||||
# Install
|
||||
This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions.
|
||||
|
||||
|
||||
To clone the extension and load locally...
|
||||
|
||||
```
|
||||
git clone https://github.com/Microsoft/vscode-SpellMD.git
|
||||
npm install
|
||||
tsc
|
||||
```
|
||||
|
||||
>**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`.
|
||||
|
||||
Copy the extension folder into user settings.
|
||||
|
||||
Depending on your platform, this folder is located here:
|
||||
* **Windows** `%USERPROFILE%\.vscode\extensions`
|
||||
* **Mac** `$HOME/.vscode/extensions`
|
||||
* **Linux** `$HOME/.vscode/extensions`
|
||||
|
||||
# Run and Configure
|
||||
|
||||
## Enable via Config setting
|
||||
Add the following setting to your WorkSpace [or User] settings:
|
||||
|
||||
```json
|
||||
"spellMD.enable": true,
|
||||
```
|
||||
|
||||
## Open a Markdown file
|
||||
Then open any Markdown file and BOOM.
|
||||
|
||||
## Configure
|
||||
The plug-in supports and watches a config file. This should go in the `.vscode` directory and needs to be called `spellMD.json`. This file has the following sections:
|
||||
* **version** incase I change the format
|
||||
* **ignoreWordsList** an array of strings that represents words not to check
|
||||
* **mistakeTypeToStatus** we detect many error types and this is how they map to VS Code severities
|
||||
* **replaceRegExp** this is an arry of RegExps represented as strings for pre-parsing the doc e.g. removing code blocks
|
||||
|
||||
> **Tip:** you need to convert any `\` from the RegExp to a `\\\\` sequence for the JSON to parse.
|
||||
|
||||
Here is an example file...
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"ignoreWordsList": [
|
||||
"IntelliSense", "project.json", "nodejs", "transpiled", "ASPNET"
|
||||
],
|
||||
"mistakeTypeToStatus": {
|
||||
"Passive voice": "Info",
|
||||
"Spelling": "Error",
|
||||
"Complex Expression": "Info",
|
||||
"Hidden Verbs": "Info",
|
||||
"Hyphen Required": "Error",
|
||||
"Did you mean...": "Info",
|
||||
"Repeated Word": "Error",
|
||||
"Missing apostrophe": "Error",
|
||||
"Redundant Expression": "Info",
|
||||
"Cliches": "Warn",
|
||||
"Missing Word": "Warn",
|
||||
"Make I uppercase": "Error"
|
||||
},
|
||||
"replaceRegExp": [
|
||||
"/^((`{3}\\\\s*)(\\\\w+)?(\\\\s*([\\\\w\\\\W]+?)\\\\n*)\\\\2)\\\\n*(?:[^\\\\S\\\\w\\\\s]|$)/gm",
|
||||
"/\\\\]\\\\(([^\\\\)]+)\\\\)/g"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
# Backlog
|
||||
|
||||
Here are some ideas - fell free to add more.
|
||||
|
||||
1. Let the user act on the suggestions e.g. a right-click or `Alt+S` opens command palette with suggestions, selection replaces.
|
||||
2. Include the Text Tools extension w/ this i.e. WordCount, HTMLEncode etc
|
||||
3. On project open check every file in the background
|
||||
1. Have an `excludeFilesList` in the options
|
||||
2. Suppress some types of issue completely i.e. don't report `Cliches` less noise in the list
|
||||
4. Provide an action to add a word to the dictionary e.g. `Alt+A`
|
||||
1. Automatically create a spellMD.json file when a user adds a word
|
||||
2. When adding a word also add plurals/sentence case etc
|
||||
|
||||
|
||||
# Debug This Code
|
||||
Run this command in the directory w/ markdown files to check.
|
||||
|
||||
```
|
||||
code --debugLanguageWorker=* --extensionDevelopmentPath="c:\src\vscode-SpellMD" .
|
||||
```
|
||||
|
||||
Then open `VS Code` in the project directory and `Attach` to the running process.
|
41
src/test/fixtures/readme/readme.expected.md
vendored
Normal file
41
src/test/fixtures/readme/readme.expected.md
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
# README
|
||||
|
||||
>**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file.
|
||||
|
||||
This README covers off:
|
||||
* [Functionality](#functionality)
|
||||
* [Install](#install)
|
||||
* [Run and Configure](#run-and-configure)
|
||||
* [Known Issues/Bugs](#known-issuesbugs)
|
||||
* [Backlog](#backlog)
|
||||
* [How to Debug](#how-to-debug)
|
||||
|
||||
# Functionality
|
||||
|
||||
Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document.
|
||||
|
||||
![Underscores and hovers](https://raw.githubusercontent.com/username/repository/master/images/SpellMDDemo1.gif)
|
||||
|
||||
The status bar lets you quickly navigate to any issue and you can see all positions in the gutter.
|
||||
|
||||
![Jump to issues](https://raw.githubusercontent.com/username/repository/master/images/SpellMDDemo2.gif)
|
||||
|
||||
The `spellMD.json` config file is watched so you can add more ignores or change mappings at will.
|
||||
|
||||
![Add to dictionary](https://raw.githubusercontent.com/username/repository/master/images/SpellMDDemo3.gif)
|
||||
|
||||
![issue](https://raw.githubusercontent.com/username/repository/master/issue)
|
||||
|
||||
# Install
|
||||
This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions.
|
||||
|
||||
|
||||
To clone the extension and load locally...
|
||||
|
||||
```
|
||||
git clone https://github.com/Microsoft/vscode-SpellMD.git
|
||||
npm install
|
||||
tsc
|
||||
```
|
||||
|
||||
>**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`.
|
41
src/test/fixtures/readme/readme.md
vendored
Normal file
41
src/test/fixtures/readme/readme.md
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
# README
|
||||
|
||||
>**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file.
|
||||
|
||||
This README covers off:
|
||||
* [Functionality](#functionality)
|
||||
* [Install](#install)
|
||||
* [Run and Configure](#run-and-configure)
|
||||
* [Known Issues/Bugs](#known-issuesbugs)
|
||||
* [Backlog](#backlog)
|
||||
* [How to Debug](#how-to-debug)
|
||||
|
||||
# Functionality
|
||||
|
||||
Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document.
|
||||
|
||||
![Underscores and hovers](/images/SpellMDDemo1.gif)
|
||||
|
||||
The status bar lets you quickly navigate to any issue and you can see all positions in the gutter.
|
||||
|
||||
![Jump to issues](/images/SpellMDDemo2.gif)
|
||||
|
||||
The `spellMD.json` config file is watched so you can add more ignores or change mappings at will.
|
||||
|
||||
![Add to dictionary](/images/SpellMDDemo3.gif)
|
||||
|
||||
![issue](issue)
|
||||
|
||||
# Install
|
||||
This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions.
|
||||
|
||||
|
||||
To clone the extension and load locally...
|
||||
|
||||
```
|
||||
git clone https://github.com/Microsoft/vscode-SpellMD.git
|
||||
npm install
|
||||
tsc
|
||||
```
|
||||
|
||||
>**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`.
|
|
@ -1,4 +1,4 @@
|
|||
import { readManifest, collect, toVsixManifest, toContentTypes } from '../package';
|
||||
import { readManifest, collect, toVsixManifest, toContentTypes, ReadmeProcessor, read } from '../package';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as assert from 'assert';
|
||||
|
@ -16,7 +16,7 @@ describe('collect', () => {
|
|||
const cwd = fixture('uuid');
|
||||
|
||||
return readManifest(cwd)
|
||||
.then(manifest => collect(cwd, manifest))
|
||||
.then(manifest => collect(manifest, { cwd }))
|
||||
.then(files => {
|
||||
assert.equal(files.length, 3);
|
||||
});
|
||||
|
@ -34,7 +34,7 @@ describe('collect', () => {
|
|||
}
|
||||
|
||||
return readManifest(cwd)
|
||||
.then(manifest => collect(cwd, manifest))
|
||||
.then(manifest => collect(manifest, { cwd }))
|
||||
.then(files => {
|
||||
assert.equal(files.length, 3);
|
||||
});
|
||||
|
@ -44,7 +44,7 @@ describe('collect', () => {
|
|||
const cwd = fixture('devDependencies');
|
||||
|
||||
return readManifest(cwd)
|
||||
.then(manifest => collect(cwd, manifest))
|
||||
.then(manifest => collect(manifest, { cwd }))
|
||||
.then(files => {
|
||||
assert.equal(files.length, 4);
|
||||
assert.ok(files.some(f => /real\/dependency\.js/.test(f.path)));
|
||||
|
@ -312,22 +312,90 @@ describe('toContentTypes', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('readmeMassaging', () => {
|
||||
it('should prepend links', () => {
|
||||
return util.massageMarkdownLinks(path.join(process.cwd(), '/src/test/assets/relativeLinks1.md'), 'https://github.com/Microsoft/')
|
||||
.then(result => readFile(path.join(process.cwd(), '/src/test/assets/absoluteLinks1.md'), 'utf-8')
|
||||
.then(expected => {
|
||||
assert.equal(result, expected);
|
||||
})
|
||||
);
|
||||
describe('ReadmeProcessor', () => {
|
||||
|
||||
it('should be no-op when no baseContentUri is provided', () => {
|
||||
const manifest = {
|
||||
name: 'test',
|
||||
publisher: 'mocha',
|
||||
version: '0.0.1',
|
||||
description: 'test extension',
|
||||
engines: Object.create(null)
|
||||
};
|
||||
|
||||
const root = fixture('readme');
|
||||
const processor = new ReadmeProcessor(manifest);
|
||||
const readme = {
|
||||
path: 'extension/readme.md',
|
||||
localPath: path.join(root, 'readme.md')
|
||||
};
|
||||
|
||||
return processor.onFile(readme)
|
||||
.then(file => read(file))
|
||||
.then(actualBuffer => {
|
||||
const actual = actualBuffer.toString('utf8');
|
||||
|
||||
return readFile(path.join(root, 'readme.md'), 'utf8')
|
||||
.then(expected => {
|
||||
assert.equal(actual, expected);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('should prepend links 2', () => {
|
||||
return util.massageMarkdownLinks(path.join(process.cwd(), '/src/test/assets/relativeLinks2.md'), 'https://github.com/Microsoft/vscode-SpellMD/raw/master/')
|
||||
.then(result => readFile(path.join(process.cwd(), '/src/test/assets/absoluteLinks2.md'), 'utf-8')
|
||||
.then(expected => {
|
||||
assert.equal(result, expected);
|
||||
})
|
||||
);
|
||||
|
||||
it('should take baseContentUri', () => {
|
||||
const manifest = {
|
||||
name: 'test',
|
||||
publisher: 'mocha',
|
||||
version: '0.0.1',
|
||||
description: 'test extension',
|
||||
engines: Object.create(null)
|
||||
};
|
||||
|
||||
const root = fixture('readme');
|
||||
const processor = new ReadmeProcessor(manifest, { baseContentUri: 'https://raw.githubusercontent.com/username/repository/master' });
|
||||
const readme = {
|
||||
path: 'extension/readme.md',
|
||||
localPath: path.join(root, 'readme.md')
|
||||
};
|
||||
|
||||
return processor.onFile(readme)
|
||||
.then(file => read(file))
|
||||
.then(actualBuffer => {
|
||||
const actual = actualBuffer.toString('utf8');
|
||||
|
||||
return readFile(path.join(root, 'readme.expected.md'), 'utf8')
|
||||
.then(expected => {
|
||||
assert.equal(actual, expected);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('should infer baseContentUri if its a github repo', () => {
|
||||
const manifest = {
|
||||
name: 'test',
|
||||
publisher: 'mocha',
|
||||
version: '0.0.1',
|
||||
description: 'test extension',
|
||||
engines: Object.create(null),
|
||||
repository: 'https://github.com/username/repository'
|
||||
};
|
||||
|
||||
const root = fixture('readme');
|
||||
const processor = new ReadmeProcessor(manifest);
|
||||
const readme = {
|
||||
path: 'extension/readme.md',
|
||||
localPath: path.join(root, 'readme.md')
|
||||
};
|
||||
|
||||
return processor.onFile(readme)
|
||||
.then(file => read(file))
|
||||
.then(actualBuffer => {
|
||||
const actual = actualBuffer.toString('utf8');
|
||||
|
||||
return readFile(path.join(root, 'readme.expected.md'), 'utf8')
|
||||
.then(expected => {
|
||||
assert.equal(actual, expected);
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
18
src/util.ts
18
src/util.ts
|
@ -5,7 +5,6 @@ import * as path from 'path';
|
|||
import { WebApi, getBasicHandler } from 'vso-node-api/WebApi';
|
||||
import { IGalleryApi, IQGalleryApi } from 'vso-node-api/GalleryApi';
|
||||
import * as denodeify from 'denodeify';
|
||||
import urljoin = require('url-join');
|
||||
|
||||
const readFile = denodeify<string, string, string>(fs.readFile);
|
||||
|
||||
|
@ -48,13 +47,14 @@ export function normalize(path: string): string {
|
|||
return path.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
export function massageMarkdownLinks(pathToMarkdown: string, prefix: string): Promise<string> {
|
||||
return readFile(pathToMarkdown, 'utf8').then(markdown => markdown.replace(/\[([^\[]+)\]\(([^\)]+)\)/g, (titleAndLink, title, link) =>
|
||||
titleAndLink.substr(0, title.length) + titleAndLink.substr(title.length).replace(link, prependRelativeLink(link, prefix))
|
||||
));
|
||||
function chain2<A,B>(a: A, b: B[], fn: (a: A, b: B)=>Promise<A>, index = 0): Promise<A> {
|
||||
if (index >= b.length) {
|
||||
return Promise.resolve(a);
|
||||
}
|
||||
|
||||
return fn(a, b[index]).then(a => chain2(a, b, fn, index + 1));
|
||||
}
|
||||
|
||||
function prependRelativeLink(link: string, prefix: string): string {
|
||||
// Prepend only relative links, also ignore links to the sections in markdown (they contain #).
|
||||
return /^(?:\w+:)\/\//.test(link) || link.indexOf('#') !== -1 ? link : urljoin(prefix, link);
|
||||
}
|
||||
export function chain<T,P>(initial: T, processors: P[], process: (a: T, b: P)=>Promise<T>): Promise<T> {
|
||||
return chain2(initial, processors, process);
|
||||
}
|
2
typings/url-join/url-join.d.ts
vendored
2
typings/url-join/url-join.d.ts
vendored
|
@ -1,5 +1,7 @@
|
|||
declare module 'url-join' {
|
||||
function join(...args: string[]): string;
|
||||
|
||||
module join {}
|
||||
|
||||
export = join;
|
||||
}
|
Loading…
Add table
Reference in a new issue