summaryrefslogtreecommitdiffstats
path: root/lib/models
diff options
context:
space:
mode:
Diffstat (limited to 'lib/models')
-rw-r--r--lib/models/__tests__/plugin.js29
-rw-r--r--lib/models/book.js146
-rw-r--r--lib/models/config.js47
-rw-r--r--lib/models/file.js55
-rw-r--r--lib/models/fs.js277
-rw-r--r--lib/models/generator.js13
-rw-r--r--lib/models/glossary.js86
-rw-r--r--lib/models/glossaryEntry.js45
-rw-r--r--lib/models/language.js21
-rw-r--r--lib/models/languages.js37
-rw-r--r--lib/models/page.js39
-rw-r--r--lib/models/plugin.js84
-rw-r--r--lib/models/readme.js30
-rw-r--r--lib/models/summary.js41
-rw-r--r--lib/models/summaryArticle.js100
-rw-r--r--lib/models/summaryPart.js42
16 files changed, 1092 insertions, 0 deletions
diff --git a/lib/models/__tests__/plugin.js b/lib/models/__tests__/plugin.js
new file mode 100644
index 0000000..81d9d51
--- /dev/null
+++ b/lib/models/__tests__/plugin.js
@@ -0,0 +1,29 @@
+jest.autoMockOff();
+
+describe('Plugin', function() {
+ var Plugin = require('../plugin');
+
+ describe('createFromString', function() {
+ it('must parse name', function() {
+ var plugin = Plugin.createFromString('hello');
+ expect(plugin.getName()).toBe('hello');
+ expect(plugin.getVersion()).toBe('*');
+ });
+
+ it('must parse version', function() {
+ var plugin = Plugin.createFromString('hello@1.0.0');
+ expect(plugin.getName()).toBe('hello');
+ expect(plugin.getVersion()).toBe('1.0.0');
+ });
+ });
+
+ describe('isLoaded', function() {
+ it('must return false for empty plugin', function() {
+ var plugin = Plugin.createFromString('hello');
+ expect(plugin.isLoaded()).toBe(false);
+ });
+
+ });
+});
+
+
diff --git a/lib/models/book.js b/lib/models/book.js
new file mode 100644
index 0000000..da0deee
--- /dev/null
+++ b/lib/models/book.js
@@ -0,0 +1,146 @@
+var path = require('path');
+var Immutable = require('immutable');
+var Ignore = require('ignore');
+
+var Logger = require('../utils/logger');
+
+var FS = require('./fs');
+var Config = require('./config');
+var Readme = require('./readme');
+var Summary = require('./summary');
+var Glossary = require('./glossary');
+var Languages = require('./languages');
+
+
+var Book = Immutable.Record({
+ // Logger for outptu message
+ logger: Logger(),
+
+ // Filesystem binded to the book scope to read files/directories
+ fs: FS(),
+
+ // Ignore files parser
+ ignore: Ignore(),
+
+ // Structure files
+ config: Config(),
+ readme: Readme(),
+ summary: Summary(),
+ glossary: Glossary(),
+ languages: Languages()
+});
+
+Book.prototype.getLogger = function() {
+ return this.get('logger');
+};
+
+Book.prototype.getFS = function() {
+ return this.get('fs');
+};
+
+Book.prototype.getIgnore = function() {
+ return this.get('ignore');
+};
+
+Book.prototype.getConfig = function() {
+ return this.get('config');
+};
+
+Book.prototype.getReadme = function() {
+ return this.get('readme');
+};
+
+Book.prototype.getSummary = function() {
+ return this.get('summary');
+};
+
+Book.prototype.getGlossary = function() {
+ return this.get('glossary');
+};
+
+Book.prototype.getLanguages = function() {
+ return this.get('languages');
+};
+
+Book.prototype.getPages = function() {
+ return this.get('pages');
+};
+
+/**
+ Return FS instance to access the content
+
+ @return {FS}
+*/
+Book.prototype.getContentFS = function() {
+ var fs = this.getFS();
+ var config = this.getConfig();
+ var rootFolder = config.getValue('root');
+
+ if (rootFolder) {
+ return FS.reduceScope(fs, rootFolder);
+ }
+
+ return fs;
+};
+
+/**
+ Return root of the book
+
+ @return {String}
+*/
+Book.prototype.getRoot = function() {
+ var fs = this.getFS();
+ return fs.getRoot();
+};
+
+/**
+ Check if a file is ignore (should not being parsed, etc)
+
+ @param {String} ref
+ @return {Page|undefined}
+*/
+Book.prototype.isFileIgnored = function(filename) {
+ var ignore = this.getIgnore();
+ return ignore.filter([filename]).length == 0;
+};
+
+/**
+ Check if a content file is ignore (should not being parsed, etc)
+
+ @param {String} ref
+ @return {Page|undefined}
+*/
+Book.prototype.isContentFileIgnored = function(filename) {
+ var config = this.getConfig();
+ var rootFolder = config.getValue('root');
+
+ if (rootFolder) {
+ filename = path.join(rootFolder, filename);
+ }
+
+ return this.isFileIgnored(filename);
+};
+
+/**
+ Return a page from a book by its path
+
+ @param {String} ref
+ @return {Page|undefined}
+*/
+Book.prototype.getPage = function(ref) {
+ return this.getPages().get(ref);
+};
+
+/**
+ Create a book using a filesystem
+
+ @param {FS} fs
+ @return {Book}
+*/
+Book.createForFS = function createForFS(fs) {
+ return new Book({
+ fs: fs
+ });
+};
+
+module.exports = Book;
diff --git a/lib/models/config.js b/lib/models/config.js
new file mode 100644
index 0000000..fd4201d
--- /dev/null
+++ b/lib/models/config.js
@@ -0,0 +1,47 @@
+var is = require('is');
+var Immutable = require('immutable');
+
+var File = require('./file');
+
+var Config = Immutable.Record({
+ file: File(),
+ values: Immutable.Map()
+});
+
+Config.prototype.getPath = function() {
+ return this.get('path');
+};
+
+Config.prototype.getValues = function() {
+ return this.get('values');
+};
+
+/**
+ Return a configuration value by its key path
+
+ @param {String} key
+ @return {Mixed}
+*/
+Config.prototype.getValue = function(keyPath, def) {
+ var values = this.getValues();
+ if (is.string(keyPath)) keyPath = keyPath.split('.');
+
+ return values.getIn(keyPath) || def;
+};
+
+/**
+ Create a new config, throw error if invalid
+
+ @param {File} file
+ @param {Object} values
+ @returns {Config}
+*/
+Config.create = function(file, values) {
+ return new Config({
+ file: file,
+ values: Immutable.fromJS(values)
+ });
+};
+
+
+module.exports = Config;
diff --git a/lib/models/file.js b/lib/models/file.js
new file mode 100644
index 0000000..ebfe629
--- /dev/null
+++ b/lib/models/file.js
@@ -0,0 +1,55 @@
+var path = require('path');
+var Immutable = require('immutable');
+
+var parsers = require('../parsers');
+
+var File = Immutable.Record({
+ // Path of the file, relative to the FS
+ path: String(),
+
+ // Time when file data last modified
+ mtime: Date()
+});
+
+File.prototype.getPath = function() {
+ return this.get('path');
+};
+
+File.prototype.getMTime = function() {
+ return this.get('mtime');
+};
+
+/**
+ Return extension of this file (lowercased)
+
+ @return {String}
+*/
+File.prototype.getExtension = function() {
+ return path.extname(this.getPath()).toLowerCase();
+};
+
+/**
+ Return parser for this file
+
+ @return {Parser}
+*/
+File.prototype.getParser = function() {
+ return parsers.getByExt(this.getExtension());
+};
+
+/**
+ Create a file from stats informations
+
+ @param {String} filepath
+ @param {Object|fs.Stats} stat
+ @return {File}
+*/
+File.createFromStat = function createFromStat(filepath, stat) {
+ return new File({
+ path: filepath,
+ mtime: stat.mtime
+ });
+};
+
+
+module.exports = File;
diff --git a/lib/models/fs.js b/lib/models/fs.js
new file mode 100644
index 0000000..2400ff2
--- /dev/null
+++ b/lib/models/fs.js
@@ -0,0 +1,277 @@
+var path = require('path');
+var Immutable = require('immutable');
+
+var File = require('./file');
+var Promise = require('../utils/promise');
+var error = require('../utils/error');
+var PathUtil = require('../utils/path');
+
+var FS = Immutable.Record({
+ root: String(),
+
+ fsExists: Function(),
+ fsReadFile: Function(),
+ fsStatFile: Function(),
+ fsReadDir: Function(),
+ fsLoadObject: null
+});
+
+/**
+ Return path to the root
+
+ @return {String}
+*/
+FS.prototype.getRoot = function() {
+ return this.get('root');
+};
+
+/**
+ Verify that a file is in the fs scope
+
+ @param {String} filename
+ @return {Boolean}
+*/
+FS.prototype.isInScope = function(filename) {
+ var rootPath = this.getRoot();
+ filename = path.resolve(rootPath, filename);
+ return PathUtil.isInRoot(rootPath, filename);
+};
+
+/**
+ Resolve a file in this FS
+
+ @param {String}
+ @return {String}
+*/
+FS.prototype.resolve = function() {
+ var rootPath = this.getRoot();
+ var args = Array.prototype.slice.call(arguments);
+ var filename = path.resolve.apply(path, [rootPath].concat(args));
+
+ if (!this.isInScope(filename)) {
+ throw error.FileOutOfScopeError({
+ filename: filename,
+ root: this.root
+ });
+ }
+
+ return filename;
+};
+
+/**
+ Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
+
+ @param {String} filename
+ @return {Promise<Boolean>}
+*/
+FS.prototype.exists = function(filename) {
+ var that = this;
+
+ return Promise()
+ .then(function() {
+ filename = that.resolve(filename);
+ var exists = that.get('fsExists');
+
+ return exists(filename);
+ });
+};
+
+/**
+ Read a file and returns a promise with the content as a buffer
+
+ @param {String} filename
+ @return {Promise<Buffer>}
+*/
+FS.prototype.read = function(filename) {
+ var that = this;
+
+ return Promise()
+ .then(function() {
+ filename = that.resolve(filename);
+ var read = that.get('fsReadFile');
+
+ return read(filename);
+ });
+};
+
+/**
+ Read a file as a string (utf-8)
+
+ @param {String} filename
+ @return {Promise<String>}
+*/
+FS.prototype.readAsString = function(filename, encoding) {
+ encoding = encoding || 'utf8';
+
+ return this.read(filename)
+ .then(function(buf) {
+ return buf.toString(encoding);
+ });
+};
+
+/**
+ Read stat infos about a file
+
+ @param {String} filename
+ @return {Promise<File>}
+*/
+FS.prototype.statFile = function(filename) {
+ var that = this;
+
+ return Promise()
+ .then(function() {
+ var filepath = that.resolve(filename);
+ var stat = that.get('fsStatFile');
+
+ return stat(filepath);
+ })
+ .then(function(stat) {
+ return File.createFromStat(filename, stat);
+ });
+};
+
+/**
+ List files/directories in a directory.
+ Directories ends with '/'
+
+ @param {String} dirname
+ @return {Promise<List<String>>}
+*/
+FS.prototype.readDir = function(dirname) {
+ var that = this;
+
+ return Promise()
+ .then(function() {
+ var dirpath = that.resolve(dirname);
+ var readDir = that.get('fsReadDir');
+
+ return readDir(dirpath);
+ })
+ .then(function(files) {
+ return Immutable.List(files);
+ });
+};
+
+/**
+ List only files in a diretcory
+ Directories ends with '/'
+
+ @param {String} dirname
+ @return {Promise<List<String>>}
+*/
+FS.prototype.listFiles = function(dirname) {
+ return this.readDir(dirname)
+ .then(function(files) {
+ return files.filterNot(pathIsFolder);
+ });
+};
+
+/**
+ List all files in a directory
+
+ @param {String} dirname
+ @return {Promise<List<String>>}
+*/
+FS.prototype.listAllFiles = function(folder) {
+ var that = this;
+ folder = folder || '.';
+
+ return this.readDir(folder)
+ .then(function(files) {
+ return Promise.reduce(files, function(out, file) {
+ var isDirectory = pathIsFolder(file);
+
+ if (!isDirectory) {
+ return out.push(file);
+ }
+
+ return that.listAllFiles(path.join(folder, file))
+ .then(function(inner) {
+ return out.concat(inner);
+ });
+ }, Immutable.List());
+ })
+ .then(function(files) {
+ return files.map(function(file) {
+ return path.join(folder, file);
+ });
+ });
+};
+
+/**
+ Find a file in a folder (case incensitive)
+ Return the found filename
+
+ @param {String} dirname
+ @param {String} filename
+ @return {Promise<String>}
+*/
+FS.prototype.findFile = function(dirname, filename) {
+ return this.listFiles(dirname)
+ .then(function(files) {
+ return files.find(function(file) {
+ return (file.toLowerCase() == filename.toLowerCase());
+ });
+ });
+};
+
+/**
+ Load a JSON file
+ By default, fs only supports JSON
+
+ @param {String} filename
+ @return {Promise<Object>}
+*/
+FS.prototype.loadAsObject = function(filename) {
+ var that = this;
+ var fsLoadObject = this.get('fsLoadObject');
+
+ return this.exists(filename)
+ .then(function(exists) {
+ if (!exists) {
+ var err = new Error('Module doesn\'t exist');
+ err.code = 'MODULE_NOT_FOUND';
+
+ throw err;
+ }
+
+ if (fsLoadObject) {
+ return fsLoadObject(that.resolve(filename));
+ } else {
+ return that.readAsString(filename)
+ .then(function(str) {
+ return JSON.parse(str);
+ });
+ }
+ });
+};
+
+/**
+ Create a FS instance
+
+ @param {Object} def
+ @return {FS}
+*/
+FS.create = function create(def) {
+ return new FS(def);
+};
+
+/**
+ Create a new FS instance with a reduced scope
+
+ @param {FS} fs
+ @param {String} scope
+ @return {FS}
+*/
+FS.reduceScope = function reduceScope(fs, scope) {
+ return fs.set('root', path.join(fs.getRoot(), scope));
+};
+
+
+// .readdir return files/folder as a list of string, folder ending with '/'
+function pathIsFolder(filename) {
+ var lastChar = filename[filename.length - 1];
+ return lastChar == '/' || lastChar == '\\';
+}
+
+module.exports = FS; \ No newline at end of file
diff --git a/lib/models/generator.js b/lib/models/generator.js
new file mode 100644
index 0000000..afc65b1
--- /dev/null
+++ b/lib/models/generator.js
@@ -0,0 +1,13 @@
+var Immutable = require('immutable');
+
+var Generator = Immutable.Record({
+ name: String()
+});
+
+
+
+Generator.create = function(def) {
+ return new Generator(def);
+};
+
+module.exports = Generator;
diff --git a/lib/models/glossary.js b/lib/models/glossary.js
new file mode 100644
index 0000000..51aa370
--- /dev/null
+++ b/lib/models/glossary.js
@@ -0,0 +1,86 @@
+var Immutable = require('immutable');
+
+var File = require('./file');
+var GlossaryEntry = require('./glossaryEntry');
+
+var Glossary = Immutable.Record({
+ file: File(),
+ entries: Immutable.OrderedMap()
+});
+
+Glossary.prototype.getFile = function() {
+ return this.get('file');
+};
+
+Glossary.prototype.getEntries = function() {
+ return this.get('entries');
+};
+
+/**
+ Return an entry by its name
+
+ @param {String} name
+ @return {GlossaryEntry}
+*/
+Glossary.prototype.getEntry = function(name) {
+ var entries = this.getEntries();
+ var id = GlossaryEntry.nameToID(name);
+
+ return entries.get(id);
+};
+
+/**
+ Add/Replace an entry to a glossary
+
+ @param {Glossary} glossary
+ @param {GlossaryEntry} entry
+ @return {Glossary}
+*/
+Glossary.addEntry = function addEntry(glossary, entry) {
+ var id = entry.getID();
+ var entries = glossary.getEntries();
+
+ entries = entries.set(id, entry);
+ return glossary.set('entries', entries);
+};
+
+/**
+ Add/Replace an entry to a glossary by name/description
+
+ @param {Glossary} glossary
+ @param {GlossaryEntry} entry
+ @return {Glossary}
+*/
+Glossary.addEntryByName = function addEntryByName(glossary, name, description) {
+ var entry = new GlossaryEntry({
+ name: name,
+ description: description
+ });
+
+ return Glossary.addEntry(glossary, entry);
+};
+
+/**
+ Create a glossary from a list of entries
+
+ @param {String} filename
+ @param {Array|List} entries
+ @return {Glossary}
+*/
+Glossary.createFromEntries = function createFromEntries(file, entries) {
+ entries = entries.map(function(entry) {
+ if (!(entry instanceof GlossaryEntry)) {
+ entry = new GlossaryEntry(entry);
+ }
+
+ return [entry.getID(), entry];
+ });
+
+ return new Glossary({
+ file: file,
+ entries: Immutable.OrderedMap(entries)
+ });
+};
+
+
+module.exports = Glossary;
diff --git a/lib/models/glossaryEntry.js b/lib/models/glossaryEntry.js
new file mode 100644
index 0000000..9c390c5
--- /dev/null
+++ b/lib/models/glossaryEntry.js
@@ -0,0 +1,45 @@
+var Immutable = require('immutable');
+
+/*
+ A definition represents an entry in the glossary
+*/
+
+var GlossaryEntry = Immutable.Record({
+ name: String(),
+ description: String()
+});
+
+GlossaryEntry.prototype.getName = function() {
+ return this.get('name');
+};
+
+GlossaryEntry.prototype.getDescription = function() {
+ return this.get('description');
+};
+
+
+/**
+ Get identifier for this entry
+
+ @retrun {Boolean}
+*/
+GlossaryEntry.prototype.getID = function() {
+ return GlossaryEntry.nameToID(this.getName());
+};
+
+
+/**
+ Normalize a glossary entry name into a unique id
+
+ @param {String}
+ @return {String}
+*/
+GlossaryEntry.nameToID = function nameToID(name) {
+ return name.toLowerCase()
+ .replace(/[\/\\\?\%\*\:\;\|\"\'\\<\\>\#\$\(\)\!\.\@]/g, '')
+ .replace(/ /g, '_')
+ .trim();
+};
+
+
+module.exports = GlossaryEntry;
diff --git a/lib/models/language.js b/lib/models/language.js
new file mode 100644
index 0000000..dcefbf6
--- /dev/null
+++ b/lib/models/language.js
@@ -0,0 +1,21 @@
+var path = require('path');
+var Immutable = require('immutable');
+
+var Language = Immutable.Record({
+ title: String(),
+ path: String()
+});
+
+Language.prototype.getTitle = function() {
+ return this.get('title');
+};
+
+Language.prototype.getPath = function() {
+ return this.get('path');
+};
+
+Language.prototype.getID = function() {
+ return path.basename(this.getPath());
+};
+
+module.exports = Language;
diff --git a/lib/models/languages.js b/lib/models/languages.js
new file mode 100644
index 0000000..c64857f
--- /dev/null
+++ b/lib/models/languages.js
@@ -0,0 +1,37 @@
+var Immutable = require('immutable');
+
+var File = require('./file');
+
+var Languages = Immutable.Record({
+ file: File(),
+ list: Immutable.OrderedMap()
+});
+
+Languages.prototype.getFile = function() {
+ return this.get('file');
+};
+
+Languages.prototype.getList = function() {
+ return this.get('list');
+};
+
+/**
+ Get default languages
+
+ @return {Language}
+*/
+Languages.prototype.getDefaultLanguage = function() {
+ return this.getList().first();
+};
+
+/**
+ Get a language by its ID
+
+ @param {String} lang
+ @return {Language}
+*/
+Languages.prototype.getLanguage = function(lang) {
+ return this.getList().get(lang);
+};
+
+module.exports = Languages;
diff --git a/lib/models/page.js b/lib/models/page.js
new file mode 100644
index 0000000..1ac0d50
--- /dev/null
+++ b/lib/models/page.js
@@ -0,0 +1,39 @@
+var Immutable = require('immutable');
+
+var File = require('./file');
+
+var Page = Immutable.Record({
+ file: File(),
+
+ // Attributes extracted from the YAML header
+ attributes: Immutable.Map(),
+
+ // Content of the page
+ content: String()
+});
+
+Page.prototype.getFile = function() {
+ return this.get('file');
+};
+
+Page.prototype.getAttributes = function() {
+ return this.get('attributes');
+};
+
+Page.prototype.getContent = function() {
+ return this.get('content');
+};
+
+/**
+ Create a page for a file
+
+ @param {File} file
+ @return {Page}
+*/
+Page.createForFile = function(file) {
+ return new Page({
+ file: file
+ });
+};
+
+module.exports = Page;
diff --git a/lib/models/plugin.js b/lib/models/plugin.js
new file mode 100644
index 0000000..b6affd4
--- /dev/null
+++ b/lib/models/plugin.js
@@ -0,0 +1,84 @@
+var Immutable = require('immutable');
+
+var PREFIX = require('../constants/pluginPrefix');
+var DEFAULT_VERSION = '*';
+
+var Plugin = Immutable.Record({
+ name: String(),
+
+ // Requirement version (ex: ">1.0.0")
+ version: String(DEFAULT_VERSION),
+
+ // Path to load this plugin
+ path: String(),
+
+ // Depth of this plugin in the dependency tree
+ depth: Number(0),
+
+ // Content of the "package.json"
+ package: Immutable.Map(),
+
+ // Content of the package itself
+ content: Immutable.Map()
+}, 'Plugin');
+
+Plugin.prototype.getName = function() {
+ return this.get('name');
+};
+
+Plugin.prototype.getPath = function() {
+ return this.get('path');
+};
+
+Plugin.prototype.getVersion = function() {
+ return this.get('version');
+};
+
+Plugin.prototype.getPackage = function() {
+ return this.get('package');
+};
+
+Plugin.prototype.getContent = function() {
+ return this.get('content');
+};
+
+Plugin.prototype.getDepth = function() {
+ return this.get('depth');
+};
+
+/**
+ Return the ID on NPM for this plugin
+
+ @return {String}
+*/
+Plugin.prototype.getNpmID = function() {
+ return PREFIX + this.getName();
+};
+
+/**
+ Check if a plugin is loaded
+
+ @return {Boolean}
+*/
+Plugin.prototype.isLoaded = function() {
+ return Boolean(this.getPackage().size > 0 && this.getContent().size > 0);
+};
+
+/**
+ Create a plugin from a string
+
+ @param {String}
+ @return {Plugin}
+*/
+Plugin.createFromString = function(s) {
+ var parts = s.split('@');
+ var name = parts[0];
+ var version = parts.slice(1).join('@');
+
+ return new Plugin({
+ name: name,
+ version: version || DEFAULT_VERSION
+ });
+};
+
+module.exports = Plugin;
diff --git a/lib/models/readme.js b/lib/models/readme.js
new file mode 100644
index 0000000..7a184c4
--- /dev/null
+++ b/lib/models/readme.js
@@ -0,0 +1,30 @@
+var Immutable = require('immutable');
+
+var File = require('./file');
+
+var Readme = Immutable.Record({
+ file: File(),
+ title: String(),
+ description: String()
+});
+
+Readme.prototype.getFile = function() {
+ return this.get('file');
+};
+
+/**
+ Create a new readme
+
+ @param {File} file
+ @param {Object} def
+ @return {Readme}
+*/
+Readme.create = function(file, def) {
+ return new Readme({
+ file: file,
+ title: def.title,
+ description: def.description
+ });
+};
+
+module.exports = Readme;
diff --git a/lib/models/summary.js b/lib/models/summary.js
new file mode 100644
index 0000000..3918df7
--- /dev/null
+++ b/lib/models/summary.js
@@ -0,0 +1,41 @@
+var Immutable = require('immutable');
+
+var File = require('./file');
+var SummaryPart = require('./summaryPart');
+
+var Summary = Immutable.Record({
+ file: File(),
+ parts: Immutable.List()
+});
+
+Summary.prototype.getFile = function() {
+ return this.get('file');
+};
+
+Summary.prototype.getParts = function() {
+ return this.get('parts');
+};
+
+
+/**
+ Create a new summary for a list of parts
+
+ @param {Lust|Array} parts
+ @return {Summary}
+*/
+Summary.createFromParts = function createFromParts(file, parts) {
+ parts = parts.map(function(part) {
+ if (part instanceof SummaryPart) {
+ return part;
+ }
+
+ return SummaryPart.create(part);
+ });
+
+ return new Summary({
+ file: file,
+ parts: new Immutable.List(parts)
+ });
+};
+
+module.exports = Summary;
diff --git a/lib/models/summaryArticle.js b/lib/models/summaryArticle.js
new file mode 100644
index 0000000..3d642fc
--- /dev/null
+++ b/lib/models/summaryArticle.js
@@ -0,0 +1,100 @@
+var Immutable = require('immutable');
+
+var location = require('../utils/location');
+
+/*
+ An article represents an entry in the Summary / table of Contents
+*/
+
+var SummaryArticle = Immutable.Record({
+ level: String(),
+ title: String(),
+ ref: String(),
+ articles: Immutable.List()
+});
+
+SummaryArticle.prototype.getLevel = function() {
+ return this.get('level');
+};
+
+SummaryArticle.prototype.getTitle = function() {
+ return this.get('title');
+};
+
+SummaryArticle.prototype.getRef = function() {
+ return this.get('ref');
+};
+
+SummaryArticle.prototype.getArticles = function() {
+ return this.get('articles');
+};
+
+/**
+ Get path (without anchor) to the pointing file
+
+ @return {String}
+*/
+SummaryArticle.prototype.getPath = function() {
+ var ref = this.getRef();
+ var parts = ref.split('#');
+
+ var pathname = (parts.length > 1? parts.slice(0, -1).join('#') : ref);
+
+ // Normalize path to remove ('./', etc)
+ return location.normalize(pathname);
+};
+
+/**
+ Get anchor for this article (or undefined)
+
+ @return {String}
+*/
+SummaryArticle.prototype.getAnchor = function() {
+ var ref = this.getRef();
+ var parts = ref.split('#');
+
+ var anchor = (parts.length > 1? '#' + parts[parts.length - 1] : null);
+ return anchor;
+};
+
+/**
+ Is article pointing to a page of an absolute url
+
+ @return {Boolean}
+*/
+SummaryArticle.prototype.isPage = function() {
+ return !this.isExternal() && this.getRef();
+};
+
+/**
+ Is article pointing to aan absolute url
+
+ @return {Boolean}
+*/
+SummaryArticle.prototype.isExternal = function() {
+ return location.isExternal(this.getRef());
+};
+
+/**
+ Create a SummaryArticle
+
+ @param {Object} def
+ @return {SummaryArticle}
+*/
+SummaryArticle.create = function(def) {
+ var articles = (def.articles || []).map(function(article) {
+ if (article instanceof SummaryArticle) {
+ return article;
+ }
+ return SummaryArticle.create(article);
+ });
+
+ return new SummaryArticle({
+ title: def.title,
+ ref: def.ref || def.path,
+ articles: Immutable.List(articles)
+ });
+};
+
+
+module.exports = SummaryArticle;
diff --git a/lib/models/summaryPart.js b/lib/models/summaryPart.js
new file mode 100644
index 0000000..4b41621
--- /dev/null
+++ b/lib/models/summaryPart.js
@@ -0,0 +1,42 @@
+var Immutable = require('immutable');
+
+var SummaryArticle = require('./summaryArticle');
+
+/*
+ A part represents a section in the Summary / table of Contents
+*/
+
+var SummaryPart = Immutable.Record({
+ title: String(),
+ articles: Immutable.List()
+});
+
+SummaryPart.prototype.getTitle = function() {
+ return this.get('title');
+};
+
+SummaryPart.prototype.getArticles = function() {
+ return this.get('articles');
+};
+
+/**
+ Create a SummaryPart
+
+ @param {Object} def
+ @return {SummaryPart}
+*/
+SummaryPart.create = function(def) {
+ var articles = (def.articles || []).map(function(article) {
+ if (article instanceof SummaryArticle) {
+ return article;
+ }
+ return SummaryArticle.create(article);
+ });
+
+ return new SummaryPart({
+ title: def.title,
+ articles: Immutable.List(articles)
+ });
+};
+
+module.exports = SummaryPart;