Skip to content

Commit d1b0f49

Browse files
committed
Refactor for better testability.
1 parent 02b0e11 commit d1b0f49

9 files changed

Lines changed: 266 additions & 187 deletions

package.json

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,35 @@
11
{
2-
"name": "markdown-table-prettify",
3-
"displayName": "Markdown Table Prettifier",
4-
"description": "Transforms markdown tables to be more readable.",
5-
"version": "0.0.1",
6-
"publisher": "darkriszty",
7-
"engines": {
8-
"vscode": "^1.5.0"
9-
},
10-
"categories": [
11-
"Other"
12-
],
13-
"activationEvents": [
14-
"onLanguage:markdown"
15-
],
16-
"main": "./out/src/extension",
17-
"contributes": { },
18-
"capabilities": {
19-
"documentFormattingProvider" : "true"
20-
},
21-
"scripts": {
22-
"vscode:prepublish": "tsc -p ./",
23-
"compile": "tsc -watch -p ./",
24-
"postinstall": "node ./node_modules/vscode/bin/install",
25-
"test": "node ./node_modules/vscode/bin/test"
26-
},
27-
"devDependencies": {
28-
"typescript": "^2.0.3",
29-
"vscode": "^1.0.0",
30-
"mocha": "^2.3.3",
31-
"@types/node": "^6.0.40",
32-
"@types/mocha": "^2.2.32"
33-
}
2+
"name": "markdown-table-prettify",
3+
"displayName": "Markdown Table Prettifier",
4+
"description": "Transforms markdown tables to be more readable.",
5+
"version": "0.0.1",
6+
"publisher": "darkriszty",
7+
"engines": {
8+
"vscode": "^1.5.0"
9+
},
10+
"categories": [
11+
"Other"
12+
],
13+
"activationEvents": [
14+
"onLanguage:markdown"
15+
],
16+
"main": "./out/src/extension",
17+
"contributes": { },
18+
"capabilities": {
19+
"documentFormattingProvider" : "true"
20+
},
21+
"scripts": {
22+
"vscode:prepublish": "tsc -p ./",
23+
"compile": "tsc -watch -p ./",
24+
"postinstall": "node ./node_modules/vscode/bin/install",
25+
"test": "node ./node_modules/vscode/bin/test"
26+
},
27+
"devDependencies": {
28+
"@types/mocha": "^2.2.32",
29+
"@types/node": "^6.0.40",
30+
"mocha": "^2.3.3",
31+
"typemoq": "^1.4.1",
32+
"typescript": "^2.0.3",
33+
"vscode": "^1.0.0"
34+
}
3435
}

src/extension.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
'use strict';
22
import * as vscode from 'vscode';
33
import { TableRangePrettyfier } from "./tableRangePrettyfier";
4+
import { TableFactory } from "./tableFactory";
5+
import { VsWindowLogger } from "./logger";
46

5-
// this method is called when your extension is activated
6-
// your extension is activated the very first time the command is executed
7+
// This method is called when the extension is activated.
8+
// The extension is activated the very first time the command is executed.
79
export function activate(context: vscode.ExtensionContext): void {
810
const MD_MODE: vscode.DocumentFilter = { language: "markdown", scheme: "file" };
11+
912
let disposable = vscode.languages.registerDocumentRangeFormattingEditProvider(
10-
MD_MODE, new TableRangePrettyfier()
13+
MD_MODE, new TableRangePrettyfier(new TableFactory(), new VsWindowLogger())
1114
);
1215

1316
context.subscriptions.push(disposable);
1417
}
1518

16-
// this method is called when your extension is deactivated
1719
export function deactivate() {
1820
}

src/logger.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as vscode from 'vscode';
2+
3+
export interface ILogger {
4+
logInfo(message: string): void;
5+
logError(error: Error | string): void;
6+
}
7+
8+
export class VsWindowLogger implements ILogger {
9+
10+
logInfo(message: string): void {
11+
vscode.window.showInformationMessage(message);
12+
}
13+
14+
logError(error: string | Error): void {
15+
const message: string = error instanceof Error
16+
? (<Error>error).message
17+
: error;
18+
19+
vscode.window.showErrorMessage(message);
20+
}
21+
}

src/table.ts

Lines changed: 8 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,16 @@
11
import { Column, ColumnPositioning, ColumnFactory } from "./column";
22

3-
export class Table {
4-
private _columns: Column[] = [];
5-
6-
public static create(text: string): Table {
7-
const lines = text.split(/\r\n|\r|\n/);
8-
const columns = 0;
9-
10-
// split by separators to get a 2d array remove any empty lines
11-
const rows = lines
12-
.map(l => l.split("|"))
13-
.filter(arr => !(arr.length == 1 && arr[0] == ""));
14-
15-
// remove the separator line from second line
16-
const rowsWithoutSeparator = TableValidator.containsHeaderSeparator(rows)
17-
? rows.filter((v, i) => i != 1) // remove the separator line from second line
18-
: null;
3+
export interface ITable {
4+
prettyPrint(): string;
5+
}
196

20-
if (!TableValidator.areValidRows(rowsWithoutSeparator))
21-
return null;
7+
export class Table implements ITable {
8+
private _columns: Column[] = [];
229

23-
var result = new Table();
24-
result._generateColumns(rowsWithoutSeparator);
25-
return result;
10+
constructor(rowsWithoutSeparator: string[][]) {
11+
this._generateColumns(rowsWithoutSeparator);
2612
}
2713

28-
private constructor() { }
29-
3014
private _generateColumns(rawRows: string[][]): void {
3115
for (let column of ColumnFactory.generateColumns(rawRows)) {
3216
this._columns.push(column);
@@ -51,30 +35,4 @@ export class Table {
5135

5236
return buffer;
5337
}
54-
}
55-
56-
class TableValidator {
57-
public static containsHeaderSeparator(rawRows: string[][]): boolean {
58-
if (!rawRows || rawRows.length < 1 || !rawRows[1])
59-
return false;
60-
const secondRow = rawRows[1];
61-
return secondRow.every((v, i) => this.isSeparator(v, i == 0 || i == secondRow.length - 1));
62-
}
63-
64-
private static isSeparator(cellValue: string, isFirstOrLast: boolean): boolean {
65-
if (cellValue.trim() == "-")
66-
return true;
67-
// If the first or last column is empty then it still can be a header containing separator,
68-
// for example table starting or ending with borders ("|") would produce this.
69-
if (cellValue.trim() == "" && isFirstOrLast)
70-
return true;
71-
return false;
72-
}
73-
74-
public static areValidRows(rawRows: string[][]): boolean {
75-
return !!rawRows &&
76-
rawRows.length > 1 && // at least two rows are required
77-
rawRows[0].length > 1 && // at least two columns are required
78-
rawRows.every(r => r.length == rawRows[0].length); // all rows of a column must match the length of the first row of that column
79-
}
80-
}
38+
}

src/tableFactory.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { ITable, Table } from "./table";
2+
3+
export interface ITableFactory {
4+
create(text: string): ITable;
5+
}
6+
7+
export class TableFactory implements ITableFactory {
8+
public create(text: string): ITable {
9+
const lines = text.split(/\r\n|\r|\n/);
10+
const columns = 0;
11+
12+
// split by separators to get a 2d array remove any empty lines
13+
const rows = lines
14+
.map(l => l.split("|"))
15+
.filter(arr => !(arr.length == 1 && arr[0] == ""));
16+
17+
// remove the separator line from second line
18+
const rowsWithoutSeparator = TableValidator.containsHeaderSeparator(rows)
19+
? rows.filter((v, i) => i != 1) // remove the separator line from second line
20+
: null;
21+
22+
if (!TableValidator.areValidRows(rowsWithoutSeparator))
23+
return null;
24+
25+
return new Table(rowsWithoutSeparator);
26+
}
27+
}
28+
29+
class TableValidator {
30+
public static containsHeaderSeparator(rawRows: string[][]): boolean {
31+
if (!rawRows || rawRows.length < 1 || !rawRows[1])
32+
return false;
33+
const secondRow = rawRows[1];
34+
return secondRow.every((v, i) => this.isSeparator(v, i == 0 || i == secondRow.length - 1));
35+
}
36+
37+
private static isSeparator(cellValue: string, isFirstOrLast: boolean): boolean {
38+
if (cellValue.trim() == "-")
39+
return true;
40+
// If the first or last column is empty then it still can be a header containing separator,
41+
// for example table starting or ending with borders ("|") would produce this.
42+
if (cellValue.trim() == "" && isFirstOrLast)
43+
return true;
44+
return false;
45+
}
46+
47+
public static areValidRows(rawRows: string[][]): boolean {
48+
return !!rawRows &&
49+
rawRows.length > 1 && // at least two rows are required
50+
rawRows[0].length > 1 && // at least two columns are required
51+
rawRows.every(r => r.length == rawRows[0].length); // all rows of a column must match the length of the first row of that column
52+
}
53+
}

src/tableRangePrettyfier.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import * as vscode from 'vscode';
2-
import { Table } from "./table";
2+
import { ITable, Table } from "./table";
3+
import { ITableFactory } from "./tableFactory";
4+
import { ILogger } from "./logger";
35

46
export class TableRangePrettyfier implements vscode.DocumentRangeFormattingEditProvider {
7+
8+
constructor(
9+
private _tableFactory: ITableFactory,
10+
private _logger: ILogger
11+
) { }
12+
513
provideDocumentRangeFormattingEdits(
614
document: vscode.TextDocument, range: vscode.Range,
715
options: vscode.FormattingOptions, token: vscode.CancellationToken) : vscode.TextEdit[]
@@ -13,10 +21,10 @@ export class TableRangePrettyfier implements vscode.DocumentRangeFormattingEditP
1321
const isWholeDocumentFormatting = this.isWholeDocumentFormatting(document, range);
1422
if (!isWholeDocumentFormatting) {
1523
let message: string = null;
16-
let table: Table = null;
24+
let table: ITable = null;
1725

1826
try {
19-
table = Table.create(selection);
27+
table = this._tableFactory.create(selection);
2028
if (table == null) {
2129
message = "Mismatching table column sizes.";
2230
}
@@ -27,12 +35,11 @@ export class TableRangePrettyfier implements vscode.DocumentRangeFormattingEditP
2735

2836
}
2937
catch (ex) {
30-
message = (<Error>ex).message;
31-
console.error(ex);
38+
this._logger.logError(ex);
3239
}
3340

3441
if (!!message)
35-
vscode.window.showInformationMessage(message);
42+
this._logger.logInfo(message);
3643
}
3744

3845
return result;

0 commit comments

Comments
 (0)