Skip to content

Add category tag #566

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions src/lib/converter/plugins/CategoryPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { Reflection, ContainerReflection } from '../../models/reflections/index';
import { ReflectionCategory } from '../../models/ReflectionCategory';
import { SourceDirectory } from '../../models/sources/directory';
import { Component, ConverterComponent } from '../components';
import { Converter } from '../converter';
import { Context } from '../context';
import { GroupPlugin } from './GroupPlugin';

/**
* A handler that sorts and categorizes the found reflections in the resolving phase.
*
* The handler sets the ´category´ property of all reflections.
*/
@Component({name: 'category'})
export class CategoryPlugin extends ConverterComponent {
/**
* Define the sort order of categories. By default, sort alphabetically.
*/
static WEIGHTS = [];

/**
* Create a new CategoryPlugin instance.
*/
initialize() {
this.listenTo(this.owner, {
[Converter.EVENT_RESOLVE]: this.onResolve,
[Converter.EVENT_RESOLVE_END]: this.onEndResolve
});
}

/**
* Triggered when the converter resolves a reflection.
*
* @param context The context object describing the current state the converter is in.
* @param reflection The reflection that is currently resolved.
*/
private onResolve(context: Context, reflection: Reflection) {
if (reflection instanceof ContainerReflection) {
const container = <ContainerReflection> reflection;
if (container.children && container.children.length > 0) {
container.children.sort(GroupPlugin.sortCallback);
container.categories = CategoryPlugin.getReflectionCategories(container.children);
}
if (container.categories && container.categories.length > 1) {
container.categories.sort(CategoryPlugin.sortCatCallback);
}
}
}

/**
* Triggered when the converter has finished resolving a project.
*
* @param context The context object describing the current state the converter is in.
*/
private onEndResolve(context: Context) {
function walkDirectory(directory: SourceDirectory) {
directory.categories = CategoryPlugin.getReflectionCategories(directory.getAllReflections());

for (let key in directory.directories) {
if (!directory.directories.hasOwnProperty(key)) {
continue;
}
walkDirectory(directory.directories[key]);
}
}

const project = context.project;
if (project.children && project.children.length > 0) {
project.children.sort(GroupPlugin.sortCallback);
project.categories = CategoryPlugin.getReflectionCategories(project.children);
}
if (project.categories && project.categories.length > 1) {
project.categories.sort(CategoryPlugin.sortCatCallback);
}

walkDirectory(project.directory);
project.files.forEach((file) => {
file.categories = CategoryPlugin.getReflectionCategories(file.reflections);
});
}

/**
* Create a categorized representation of the given list of reflections.
*
* @param reflections The reflections that should be categorized.
* @returns An array containing all children of the given reflection categorized
*/
static getReflectionCategories(reflections: Reflection[]): ReflectionCategory[] {
const categories: ReflectionCategory[] = [];
reflections.forEach((child) => {
const childCat = CategoryPlugin.getCategory(child);
if (childCat === '') {
return;
}
for (let i = 0; i < categories.length; i++) {
const category = categories[i];

if (category.title !== childCat) {
continue;
}

category.children.push(child);
return;
}

const category = new ReflectionCategory(childCat);
category.children.push(child);
categories.push(category);
});
return categories;
}

/**
* Return the category of a given reflection.
*
* @param reflection The reflection.
* @returns The category the reflection belongs to
*/
static getCategory(reflection: Reflection): string {
if (reflection.comment) {
const tags = reflection.comment.tags;
if (tags) {
for (let i = 0; i < tags.length; i++) {
if (tags[i].tagName === 'category') {
let tag = tags[i].text;
return (tag.charAt(0).toUpperCase() + tag.slice(1).toLowerCase()).trim();
}
}
}
}
return '';
}

/**
* Callback used to sort reflections by name.
*
* @param a The left reflection to sort.
* @param b The right reflection to sort.
* @returns The sorting weight.
*/
static sortCallback(a: Reflection, b: Reflection): number {
return a.name > b.name ? 1 : -1;
}

/**
* Callback used to sort categories by name.
*
* @param a The left reflection to sort.
* @param b The right reflection to sort.
* @returns The sorting weight.
*/
static sortCatCallback(a: ReflectionCategory, b: ReflectionCategory): number {
const aWeight = CategoryPlugin.WEIGHTS.indexOf(a.title);
const bWeight = CategoryPlugin.WEIGHTS.indexOf(b.title);
if (aWeight < 0 && bWeight < 0) {
return a.title > b.title ? 1 : -1;
}
if (aWeight < 0) {
return 1;
}
if (bWeight < 0) {
return -1;
}
return aWeight - bWeight;
}
}
1 change: 1 addition & 0 deletions src/lib/converter/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { CategoryPlugin } from './CategoryPlugin';
export { CommentPlugin } from './CommentPlugin';
export { DecoratorPlugin } from './DecoratorPlugin';
export { DeepCommentPlugin } from './DeepCommentPlugin';
Expand Down
71 changes: 71 additions & 0 deletions src/lib/models/ReflectionCategory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Reflection } from './reflections/abstract';

/**
* A category of reflections.
*
* Reflection categories are created by the ´CategoryPlugin´ in the resolving phase
* of the dispatcher. The main purpose of categories is to be able to more easily
* render human readable children lists in templates.
*/
export class ReflectionCategory {
/**
* The title, a string representation of this category.
*/
title: string;

/**
* All reflections of this category.
*/
children: Reflection[] = [];

/**
* Do all children of this category have a separate document?
*
* A bound representation of the ´ReflectionCategory.getAllChildrenHaveOwnDocument´
* that can be used within templates.
*/
allChildrenHaveOwnDocument: Function;

/**
* Create a new ReflectionCategory instance.
*
* @param title The title of this category.
*/
constructor(title: string) {
this.title = title;

this.allChildrenHaveOwnDocument = (() => this.getAllChildrenHaveOwnDocument());
}

/**
* Do all children of this category have a separate document?
*/
private getAllChildrenHaveOwnDocument(): boolean {
let onlyOwnDocuments = true;
this.children.forEach((child) => {
onlyOwnDocuments = onlyOwnDocuments && child.hasOwnDocument;
});

return onlyOwnDocuments;
}

/**
* Return a raw object representation of this reflection category.
*/
toObject(): any {
const result = {
title: this.title
};

if (this.children) {
const children: any[] = [];
this.children.forEach((child) => {
children.push(child.id);
});

result['children'] = children;
}

return result;
}
}
17 changes: 17 additions & 0 deletions src/lib/models/reflections/container.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Reflection, ReflectionKind, TraverseCallback, TraverseProperty } from './abstract';
import { ReflectionCategory } from '../ReflectionCategory';
import { ReflectionGroup } from '../ReflectionGroup';
import { DeclarationReflection } from './declaration';

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

/**
* All children grouped by their category.
*/
categories: ReflectionCategory[];

/**
* Return a list of all children of a certain kind.
*
Expand Down Expand Up @@ -61,6 +67,17 @@ export class ContainerReflection extends Reflection {
result['groups'] = groups;
}

if (this.categories) {
const categories: any[] = [];
this.categories.forEach((category) => {
categories.push(category.toObject());
});

if (categories.length > 0) {
result['categories'] = categories;
}
}

if (this.sources) {
const sources: any[] = [];
this.sources.forEach((source) => {
Expand Down
26 changes: 26 additions & 0 deletions src/lib/models/reflections/project.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SourceFile, SourceDirectory } from '../sources/index';
import { Reflection, ReflectionKind } from './abstract';
import { ContainerReflection } from './container';
import { ReflectionCategory } from '../ReflectionCategory';

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

/**
* All reflections categorized.
*/
categories: ReflectionCategory[];

/**
* The name of the project.
*
Expand Down Expand Up @@ -117,4 +123,24 @@ export class ProjectReflection extends ContainerReflection {

return null;
}

/**
* Return a raw object representation of this reflection.
*/
toObject(): any {
const result = super.toObject();

if (this.categories) {
const categories: any[] = [];
this.categories.forEach((category) => {
categories.push(category.toObject());
});

if (categories.length > 0) {
result['categories'] = categories;
}
}

return result;
}
}
3 changes: 3 additions & 0 deletions src/lib/models/sources/directory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Reflection } from '../reflections/abstract';
import { ReflectionCategory } from '../ReflectionCategory';
import { ReflectionGroup } from '../ReflectionGroup';
import { SourceFile } from './file';

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

groups: ReflectionGroup[];

categories: ReflectionCategory[];

/**
* A list of all files in this directory.
*/
Expand Down
6 changes: 6 additions & 0 deletions src/lib/models/sources/file.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as Path from 'path';

import { Reflection } from '../reflections/abstract';
import { ReflectionCategory } from '../ReflectionCategory';
import { ReflectionGroup } from '../ReflectionGroup';
import { SourceDirectory } from './directory';

Expand Down Expand Up @@ -80,6 +81,11 @@ export class SourceFile {
*/
groups: ReflectionGroup[];

/**
* A categorized list of the reflections declared in this file.
*/
categories: ReflectionCategory[];

/**
* Create a new SourceFile instance.
*
Expand Down