Skip to content

Commit 605cf23

Browse files
jonchardyblakeembrey
authored andcommitted
Support @category tag (#566)
1 parent 482821c commit 605cf23

File tree

7 files changed

+290
-0
lines changed

7 files changed

+290
-0
lines changed
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { Reflection, ContainerReflection } from '../../models/reflections/index';
2+
import { ReflectionCategory } from '../../models/ReflectionCategory';
3+
import { SourceDirectory } from '../../models/sources/directory';
4+
import { Component, ConverterComponent } from '../components';
5+
import { Converter } from '../converter';
6+
import { Context } from '../context';
7+
import { GroupPlugin } from './GroupPlugin';
8+
9+
/**
10+
* A handler that sorts and categorizes the found reflections in the resolving phase.
11+
*
12+
* The handler sets the ´category´ property of all reflections.
13+
*/
14+
@Component({name: 'category'})
15+
export class CategoryPlugin extends ConverterComponent {
16+
/**
17+
* Define the sort order of categories. By default, sort alphabetically.
18+
*/
19+
static WEIGHTS = [];
20+
21+
/**
22+
* Create a new CategoryPlugin instance.
23+
*/
24+
initialize() {
25+
this.listenTo(this.owner, {
26+
[Converter.EVENT_RESOLVE]: this.onResolve,
27+
[Converter.EVENT_RESOLVE_END]: this.onEndResolve
28+
});
29+
}
30+
31+
/**
32+
* Triggered when the converter resolves a reflection.
33+
*
34+
* @param context The context object describing the current state the converter is in.
35+
* @param reflection The reflection that is currently resolved.
36+
*/
37+
private onResolve(context: Context, reflection: Reflection) {
38+
if (reflection instanceof ContainerReflection) {
39+
const container = <ContainerReflection> reflection;
40+
if (container.children && container.children.length > 0) {
41+
container.children.sort(GroupPlugin.sortCallback);
42+
container.categories = CategoryPlugin.getReflectionCategories(container.children);
43+
}
44+
if (container.categories && container.categories.length > 1) {
45+
container.categories.sort(CategoryPlugin.sortCatCallback);
46+
}
47+
}
48+
}
49+
50+
/**
51+
* Triggered when the converter has finished resolving a project.
52+
*
53+
* @param context The context object describing the current state the converter is in.
54+
*/
55+
private onEndResolve(context: Context) {
56+
function walkDirectory(directory: SourceDirectory) {
57+
directory.categories = CategoryPlugin.getReflectionCategories(directory.getAllReflections());
58+
59+
for (let key in directory.directories) {
60+
if (!directory.directories.hasOwnProperty(key)) {
61+
continue;
62+
}
63+
walkDirectory(directory.directories[key]);
64+
}
65+
}
66+
67+
const project = context.project;
68+
if (project.children && project.children.length > 0) {
69+
project.children.sort(GroupPlugin.sortCallback);
70+
project.categories = CategoryPlugin.getReflectionCategories(project.children);
71+
}
72+
if (project.categories && project.categories.length > 1) {
73+
project.categories.sort(CategoryPlugin.sortCatCallback);
74+
}
75+
76+
walkDirectory(project.directory);
77+
project.files.forEach((file) => {
78+
file.categories = CategoryPlugin.getReflectionCategories(file.reflections);
79+
});
80+
}
81+
82+
/**
83+
* Create a categorized representation of the given list of reflections.
84+
*
85+
* @param reflections The reflections that should be categorized.
86+
* @returns An array containing all children of the given reflection categorized
87+
*/
88+
static getReflectionCategories(reflections: Reflection[]): ReflectionCategory[] {
89+
const categories: ReflectionCategory[] = [];
90+
reflections.forEach((child) => {
91+
const childCat = CategoryPlugin.getCategory(child);
92+
if (childCat === '') {
93+
return;
94+
}
95+
for (let i = 0; i < categories.length; i++) {
96+
const category = categories[i];
97+
98+
if (category.title !== childCat) {
99+
continue;
100+
}
101+
102+
category.children.push(child);
103+
return;
104+
}
105+
106+
const category = new ReflectionCategory(childCat);
107+
category.children.push(child);
108+
categories.push(category);
109+
});
110+
return categories;
111+
}
112+
113+
/**
114+
* Return the category of a given reflection.
115+
*
116+
* @param reflection The reflection.
117+
* @returns The category the reflection belongs to
118+
*/
119+
static getCategory(reflection: Reflection): string {
120+
if (reflection.comment) {
121+
const tags = reflection.comment.tags;
122+
if (tags) {
123+
for (let i = 0; i < tags.length; i++) {
124+
if (tags[i].tagName === 'category') {
125+
let tag = tags[i].text;
126+
return (tag.charAt(0).toUpperCase() + tag.slice(1).toLowerCase()).trim();
127+
}
128+
}
129+
}
130+
}
131+
return '';
132+
}
133+
134+
/**
135+
* Callback used to sort reflections by name.
136+
*
137+
* @param a The left reflection to sort.
138+
* @param b The right reflection to sort.
139+
* @returns The sorting weight.
140+
*/
141+
static sortCallback(a: Reflection, b: Reflection): number {
142+
return a.name > b.name ? 1 : -1;
143+
}
144+
145+
/**
146+
* Callback used to sort categories by name.
147+
*
148+
* @param a The left reflection to sort.
149+
* @param b The right reflection to sort.
150+
* @returns The sorting weight.
151+
*/
152+
static sortCatCallback(a: ReflectionCategory, b: ReflectionCategory): number {
153+
const aWeight = CategoryPlugin.WEIGHTS.indexOf(a.title);
154+
const bWeight = CategoryPlugin.WEIGHTS.indexOf(b.title);
155+
if (aWeight < 0 && bWeight < 0) {
156+
return a.title > b.title ? 1 : -1;
157+
}
158+
if (aWeight < 0) {
159+
return 1;
160+
}
161+
if (bWeight < 0) {
162+
return -1;
163+
}
164+
return aWeight - bWeight;
165+
}
166+
}

src/lib/converter/plugins/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export { CategoryPlugin } from './CategoryPlugin';
12
export { CommentPlugin } from './CommentPlugin';
23
export { DecoratorPlugin } from './DecoratorPlugin';
34
export { DeepCommentPlugin } from './DeepCommentPlugin';
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { Reflection } from './reflections/abstract';
2+
3+
/**
4+
* A category of reflections.
5+
*
6+
* Reflection categories are created by the ´CategoryPlugin´ in the resolving phase
7+
* of the dispatcher. The main purpose of categories is to be able to more easily
8+
* render human readable children lists in templates.
9+
*/
10+
export class ReflectionCategory {
11+
/**
12+
* The title, a string representation of this category.
13+
*/
14+
title: string;
15+
16+
/**
17+
* All reflections of this category.
18+
*/
19+
children: Reflection[] = [];
20+
21+
/**
22+
* Do all children of this category have a separate document?
23+
*
24+
* A bound representation of the ´ReflectionCategory.getAllChildrenHaveOwnDocument´
25+
* that can be used within templates.
26+
*/
27+
allChildrenHaveOwnDocument: Function;
28+
29+
/**
30+
* Create a new ReflectionCategory instance.
31+
*
32+
* @param title The title of this category.
33+
*/
34+
constructor(title: string) {
35+
this.title = title;
36+
37+
this.allChildrenHaveOwnDocument = (() => this.getAllChildrenHaveOwnDocument());
38+
}
39+
40+
/**
41+
* Do all children of this category have a separate document?
42+
*/
43+
private getAllChildrenHaveOwnDocument(): boolean {
44+
let onlyOwnDocuments = true;
45+
this.children.forEach((child) => {
46+
onlyOwnDocuments = onlyOwnDocuments && child.hasOwnDocument;
47+
});
48+
49+
return onlyOwnDocuments;
50+
}
51+
52+
/**
53+
* Return a raw object representation of this reflection category.
54+
*/
55+
toObject(): any {
56+
const result = {
57+
title: this.title
58+
};
59+
60+
if (this.children) {
61+
const children: any[] = [];
62+
this.children.forEach((child) => {
63+
children.push(child.id);
64+
});
65+
66+
result['children'] = children;
67+
}
68+
69+
return result;
70+
}
71+
}

src/lib/models/reflections/container.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Reflection, ReflectionKind, TraverseCallback, TraverseProperty } from './abstract';
2+
import { ReflectionCategory } from '../ReflectionCategory';
23
import { ReflectionGroup } from '../ReflectionGroup';
34
import { DeclarationReflection } from './declaration';
45

@@ -13,6 +14,11 @@ export class ContainerReflection extends Reflection {
1314
*/
1415
groups: ReflectionGroup[];
1516

17+
/**
18+
* All children grouped by their category.
19+
*/
20+
categories: ReflectionCategory[];
21+
1622
/**
1723
* Return a list of all children of a certain kind.
1824
*
@@ -61,6 +67,17 @@ export class ContainerReflection extends Reflection {
6167
result['groups'] = groups;
6268
}
6369

70+
if (this.categories) {
71+
const categories: any[] = [];
72+
this.categories.forEach((category) => {
73+
categories.push(category.toObject());
74+
});
75+
76+
if (categories.length > 0) {
77+
result['categories'] = categories;
78+
}
79+
}
80+
6481
if (this.sources) {
6582
const sources: any[] = [];
6683
this.sources.forEach((source) => {

src/lib/models/reflections/project.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { SourceFile, SourceDirectory } from '../sources/index';
22
import { Reflection, ReflectionKind } from './abstract';
33
import { ContainerReflection } from './container';
4+
import { ReflectionCategory } from '../ReflectionCategory';
45

56
/**
67
* A reflection that represents the root of the project.
@@ -26,6 +27,11 @@ export class ProjectReflection extends ContainerReflection {
2627
*/
2728
files: SourceFile[] = [];
2829

30+
/**
31+
* All reflections categorized.
32+
*/
33+
categories: ReflectionCategory[];
34+
2935
/**
3036
* The name of the project.
3137
*
@@ -117,4 +123,24 @@ export class ProjectReflection extends ContainerReflection {
117123

118124
return null;
119125
}
126+
127+
/**
128+
* Return a raw object representation of this reflection.
129+
*/
130+
toObject(): any {
131+
const result = super.toObject();
132+
133+
if (this.categories) {
134+
const categories: any[] = [];
135+
this.categories.forEach((category) => {
136+
categories.push(category.toObject());
137+
});
138+
139+
if (categories.length > 0) {
140+
result['categories'] = categories;
141+
}
142+
}
143+
144+
return result;
145+
}
120146
}

src/lib/models/sources/directory.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Reflection } from '../reflections/abstract';
2+
import { ReflectionCategory } from '../ReflectionCategory';
23
import { ReflectionGroup } from '../ReflectionGroup';
34
import { SourceFile } from './file';
45

@@ -22,6 +23,8 @@ export class SourceDirectory {
2223

2324
groups: ReflectionGroup[];
2425

26+
categories: ReflectionCategory[];
27+
2528
/**
2629
* A list of all files in this directory.
2730
*/

src/lib/models/sources/file.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as Path from 'path';
22

33
import { Reflection } from '../reflections/abstract';
4+
import { ReflectionCategory } from '../ReflectionCategory';
45
import { ReflectionGroup } from '../ReflectionGroup';
56
import { SourceDirectory } from './directory';
67

@@ -80,6 +81,11 @@ export class SourceFile {
8081
*/
8182
groups: ReflectionGroup[];
8283

84+
/**
85+
* A categorized list of the reflections declared in this file.
86+
*/
87+
categories: ReflectionCategory[];
88+
8389
/**
8490
* Create a new SourceFile instance.
8591
*

0 commit comments

Comments
 (0)