build(aio): move doc-gen stuff from angular.io (#14097)
This commit is contained in:

committed by
Igor Minar

parent
d1d0ce7613
commit
b7763559cd
27
tools/docs/README.md
Normal file
27
tools/docs/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# Documentation Generation
|
||||
|
||||
The dgeni tool is used to generate the documentation from the source files held in this repository.
|
||||
The documentation generation is configured by a dgeni package defined in `docs/angular.io-package/index.js`.
|
||||
This package, in turn requires a number of other packages, some are defined locally in the `docs` folder,
|
||||
such as `docs/cheatsheet-package` and `docs/content-package`, etc. And some are brought in from the
|
||||
`dgeni-packages` node modules, such as `jsdoc` and `nunjucks`.
|
||||
|
||||
## Generating the docs
|
||||
|
||||
To generate the documentation simply run `gulp docs` from the command line.
|
||||
|
||||
## Testing the dgeni packages
|
||||
|
||||
The local packages have unit tests that you can execute by running `gulp docs-test` from the command line.
|
||||
|
||||
## What does it generate?
|
||||
|
||||
The output from dgeni is written to files in the `dist/docs` folder.
|
||||
|
||||
Notably this includes a partial HTML file for each "page" of the documentation, such as API pages and guides.
|
||||
It also includes JavaScript files that contain metadata about the documentation such as navigation data and
|
||||
keywords for building a search index.
|
||||
|
||||
## Viewing the docs
|
||||
|
||||
You can view the dummy demo app using a simple HTTP server hosting `dist/docs/index.html`
|
701
tools/docs/angular.io-package/ignore.words
Normal file
701
tools/docs/angular.io-package/ignore.words
Normal file
@ -0,0 +1,701 @@
|
||||
a
|
||||
able
|
||||
about
|
||||
above
|
||||
abst
|
||||
accordance
|
||||
according
|
||||
accordingly
|
||||
across
|
||||
act
|
||||
actually
|
||||
added
|
||||
adj
|
||||
adopted
|
||||
affected
|
||||
affecting
|
||||
affects
|
||||
after
|
||||
afterwards
|
||||
again
|
||||
against
|
||||
ah
|
||||
all
|
||||
almost
|
||||
alone
|
||||
along
|
||||
already
|
||||
also
|
||||
although
|
||||
always
|
||||
am
|
||||
among
|
||||
amongst
|
||||
an
|
||||
and
|
||||
announce
|
||||
another
|
||||
any
|
||||
anybody
|
||||
anyhow
|
||||
anymore
|
||||
anyone
|
||||
anything
|
||||
anyway
|
||||
anyways
|
||||
anywhere
|
||||
apparently
|
||||
approximately
|
||||
are
|
||||
aren
|
||||
arent
|
||||
arise
|
||||
around
|
||||
as
|
||||
aside
|
||||
ask
|
||||
asking
|
||||
at
|
||||
auth
|
||||
available
|
||||
away
|
||||
awfully
|
||||
b
|
||||
back
|
||||
be
|
||||
became
|
||||
because
|
||||
become
|
||||
becomes
|
||||
becoming
|
||||
been
|
||||
before
|
||||
beforehand
|
||||
begin
|
||||
beginning
|
||||
beginnings
|
||||
begins
|
||||
behind
|
||||
being
|
||||
believe
|
||||
below
|
||||
beside
|
||||
besides
|
||||
between
|
||||
beyond
|
||||
biol
|
||||
both
|
||||
brief
|
||||
briefly
|
||||
but
|
||||
by
|
||||
c
|
||||
ca
|
||||
came
|
||||
can
|
||||
cannot
|
||||
can't
|
||||
cant
|
||||
cause
|
||||
causes
|
||||
certain
|
||||
certainly
|
||||
co
|
||||
com
|
||||
come
|
||||
comes
|
||||
contain
|
||||
containing
|
||||
contains
|
||||
could
|
||||
couldnt
|
||||
d
|
||||
date
|
||||
did
|
||||
didn't
|
||||
didnt
|
||||
different
|
||||
do
|
||||
does
|
||||
doesn't
|
||||
doesnt
|
||||
doing
|
||||
done
|
||||
don't
|
||||
dont
|
||||
down
|
||||
downwards
|
||||
due
|
||||
during
|
||||
e
|
||||
each
|
||||
ed
|
||||
edu
|
||||
effect
|
||||
eg
|
||||
eight
|
||||
eighty
|
||||
either
|
||||
else
|
||||
elsewhere
|
||||
end
|
||||
ending
|
||||
enough
|
||||
especially
|
||||
et
|
||||
et-al
|
||||
etc
|
||||
even
|
||||
ever
|
||||
every
|
||||
everybody
|
||||
everyone
|
||||
everything
|
||||
everywhere
|
||||
ex
|
||||
except
|
||||
f
|
||||
far
|
||||
few
|
||||
ff
|
||||
fifth
|
||||
first
|
||||
five
|
||||
fix
|
||||
followed
|
||||
following
|
||||
follows
|
||||
for
|
||||
former
|
||||
formerly
|
||||
forth
|
||||
found
|
||||
four
|
||||
from
|
||||
further
|
||||
furthermore
|
||||
g
|
||||
gave
|
||||
get
|
||||
gets
|
||||
getting
|
||||
give
|
||||
given
|
||||
gives
|
||||
giving
|
||||
go
|
||||
goes
|
||||
gone
|
||||
got
|
||||
gotten
|
||||
h
|
||||
had
|
||||
happens
|
||||
hardly
|
||||
has
|
||||
hasn't
|
||||
hasnt
|
||||
have
|
||||
haven't
|
||||
havent
|
||||
having
|
||||
he
|
||||
hed
|
||||
hence
|
||||
her
|
||||
here
|
||||
hereafter
|
||||
hereby
|
||||
herein
|
||||
heres
|
||||
hereupon
|
||||
hers
|
||||
herself
|
||||
hes
|
||||
hi
|
||||
hid
|
||||
him
|
||||
himself
|
||||
his
|
||||
hither
|
||||
home
|
||||
how
|
||||
howbeit
|
||||
however
|
||||
hundred
|
||||
i
|
||||
id
|
||||
ie
|
||||
if
|
||||
i'll
|
||||
ill
|
||||
im
|
||||
immediate
|
||||
immediately
|
||||
importance
|
||||
important
|
||||
in
|
||||
inc
|
||||
indeed
|
||||
index
|
||||
information
|
||||
instead
|
||||
into
|
||||
invention
|
||||
inward
|
||||
is
|
||||
isn't
|
||||
isnt
|
||||
it
|
||||
itd
|
||||
it'll
|
||||
itll
|
||||
its
|
||||
itself
|
||||
i've
|
||||
ive
|
||||
j
|
||||
just
|
||||
k
|
||||
keep
|
||||
keeps
|
||||
kept
|
||||
keys
|
||||
kg
|
||||
km
|
||||
know
|
||||
known
|
||||
knows
|
||||
l
|
||||
largely
|
||||
last
|
||||
lately
|
||||
later
|
||||
latter
|
||||
latterly
|
||||
least
|
||||
less
|
||||
lest
|
||||
let
|
||||
lets
|
||||
like
|
||||
liked
|
||||
likely
|
||||
line
|
||||
little
|
||||
'll
|
||||
'll
|
||||
look
|
||||
looking
|
||||
looks
|
||||
ltd
|
||||
m
|
||||
made
|
||||
mainly
|
||||
make
|
||||
makes
|
||||
many
|
||||
may
|
||||
maybe
|
||||
me
|
||||
mean
|
||||
means
|
||||
meantime
|
||||
meanwhile
|
||||
merely
|
||||
mg
|
||||
might
|
||||
million
|
||||
miss
|
||||
ml
|
||||
more
|
||||
moreover
|
||||
most
|
||||
mostly
|
||||
mr
|
||||
mrs
|
||||
much
|
||||
mug
|
||||
must
|
||||
my
|
||||
myself
|
||||
n
|
||||
na
|
||||
name
|
||||
namely
|
||||
nay
|
||||
nd
|
||||
near
|
||||
nearly
|
||||
necessarily
|
||||
necessary
|
||||
need
|
||||
needs
|
||||
neither
|
||||
never
|
||||
nevertheless
|
||||
new
|
||||
next
|
||||
nine
|
||||
ninety
|
||||
no
|
||||
nobody
|
||||
non
|
||||
none
|
||||
nonetheless
|
||||
noone
|
||||
nor
|
||||
normally
|
||||
nos
|
||||
not
|
||||
noted
|
||||
nothing
|
||||
now
|
||||
nowhere
|
||||
o
|
||||
obtain
|
||||
obtained
|
||||
obviously
|
||||
of
|
||||
off
|
||||
often
|
||||
oh
|
||||
ok
|
||||
okay
|
||||
old
|
||||
omitted
|
||||
on
|
||||
once
|
||||
one
|
||||
ones
|
||||
only
|
||||
onto
|
||||
or
|
||||
ord
|
||||
other
|
||||
others
|
||||
otherwise
|
||||
ought
|
||||
our
|
||||
ours
|
||||
ourselves
|
||||
out
|
||||
outside
|
||||
over
|
||||
overall
|
||||
owing
|
||||
own
|
||||
p
|
||||
page
|
||||
pages
|
||||
part
|
||||
particular
|
||||
particularly
|
||||
past
|
||||
per
|
||||
perhaps
|
||||
placed
|
||||
please
|
||||
plus
|
||||
poorly
|
||||
possible
|
||||
possibly
|
||||
potentially
|
||||
pp
|
||||
predominantly
|
||||
present
|
||||
previously
|
||||
primarily
|
||||
probably
|
||||
promptly
|
||||
proud
|
||||
provides
|
||||
put
|
||||
q
|
||||
que
|
||||
quickly
|
||||
quite
|
||||
qv
|
||||
r
|
||||
ran
|
||||
rather
|
||||
rd
|
||||
re
|
||||
readily
|
||||
really
|
||||
recent
|
||||
recently
|
||||
ref
|
||||
refs
|
||||
regarding
|
||||
regardless
|
||||
regards
|
||||
related
|
||||
relatively
|
||||
research
|
||||
respectively
|
||||
resulted
|
||||
resulting
|
||||
results
|
||||
right
|
||||
run
|
||||
s
|
||||
said
|
||||
same
|
||||
saw
|
||||
say
|
||||
saying
|
||||
says
|
||||
sec
|
||||
section
|
||||
see
|
||||
seeing
|
||||
seem
|
||||
seemed
|
||||
seeming
|
||||
seems
|
||||
seen
|
||||
self
|
||||
selves
|
||||
sent
|
||||
seven
|
||||
several
|
||||
shall
|
||||
she
|
||||
shed
|
||||
she'll
|
||||
shell
|
||||
shes
|
||||
should
|
||||
shouldn't
|
||||
shouldnt
|
||||
show
|
||||
showed
|
||||
shown
|
||||
showns
|
||||
shows
|
||||
significant
|
||||
significantly
|
||||
similar
|
||||
similarly
|
||||
since
|
||||
six
|
||||
slightly
|
||||
so
|
||||
some
|
||||
somebody
|
||||
somehow
|
||||
someone
|
||||
somethan
|
||||
something
|
||||
sometime
|
||||
sometimes
|
||||
somewhat
|
||||
somewhere
|
||||
soon
|
||||
sorry
|
||||
specifically
|
||||
specified
|
||||
specify
|
||||
specifying
|
||||
state
|
||||
states
|
||||
still
|
||||
stop
|
||||
strongly
|
||||
sub
|
||||
substantially
|
||||
successfully
|
||||
such
|
||||
sufficiently
|
||||
suggest
|
||||
sup
|
||||
sure
|
||||
t
|
||||
take
|
||||
taken
|
||||
taking
|
||||
tell
|
||||
tends
|
||||
th
|
||||
than
|
||||
thank
|
||||
thanks
|
||||
thanx
|
||||
that
|
||||
that'll
|
||||
thatll
|
||||
thats
|
||||
that've
|
||||
thatve
|
||||
the
|
||||
their
|
||||
theirs
|
||||
them
|
||||
themselves
|
||||
then
|
||||
thence
|
||||
there
|
||||
thereafter
|
||||
thereby
|
||||
thered
|
||||
therefore
|
||||
therein
|
||||
there'll
|
||||
therell
|
||||
thereof
|
||||
therere
|
||||
theres
|
||||
thereto
|
||||
thereupon
|
||||
there've
|
||||
thereve
|
||||
these
|
||||
they
|
||||
theyd
|
||||
they'll
|
||||
theyll
|
||||
theyre
|
||||
they've
|
||||
theyve
|
||||
think
|
||||
this
|
||||
those
|
||||
thou
|
||||
though
|
||||
thoughh
|
||||
thousand
|
||||
throug
|
||||
through
|
||||
throughout
|
||||
thru
|
||||
thus
|
||||
til
|
||||
tip
|
||||
to
|
||||
together
|
||||
too
|
||||
took
|
||||
toward
|
||||
towards
|
||||
tried
|
||||
tries
|
||||
truly
|
||||
try
|
||||
trying
|
||||
ts
|
||||
twice
|
||||
two
|
||||
u
|
||||
un
|
||||
under
|
||||
unfortunately
|
||||
unless
|
||||
unlike
|
||||
unlikely
|
||||
until
|
||||
unto
|
||||
up
|
||||
upon
|
||||
ups
|
||||
us
|
||||
use
|
||||
used
|
||||
useful
|
||||
usefully
|
||||
usefulness
|
||||
uses
|
||||
using
|
||||
usually
|
||||
v
|
||||
value
|
||||
various
|
||||
've
|
||||
've
|
||||
very
|
||||
via
|
||||
viz
|
||||
vol
|
||||
vols
|
||||
vs
|
||||
w
|
||||
want
|
||||
wants
|
||||
was
|
||||
wasn't
|
||||
wasnt
|
||||
way
|
||||
we
|
||||
wed
|
||||
welcome
|
||||
we'll
|
||||
well
|
||||
went
|
||||
were
|
||||
weren't
|
||||
werent
|
||||
we've
|
||||
weve
|
||||
what
|
||||
whatever
|
||||
what'll
|
||||
whatll
|
||||
whats
|
||||
when
|
||||
whence
|
||||
whenever
|
||||
where
|
||||
whereafter
|
||||
whereas
|
||||
whereby
|
||||
wherein
|
||||
wheres
|
||||
whereupon
|
||||
wherever
|
||||
whether
|
||||
which
|
||||
while
|
||||
whim
|
||||
whither
|
||||
who
|
||||
whod
|
||||
whoever
|
||||
whole
|
||||
who'll
|
||||
wholl
|
||||
whom
|
||||
whomever
|
||||
whos
|
||||
whose
|
||||
why
|
||||
widely
|
||||
will
|
||||
willing
|
||||
wish
|
||||
with
|
||||
within
|
||||
without
|
||||
won't
|
||||
wont
|
||||
words
|
||||
would
|
||||
wouldn't
|
||||
wouldnt
|
||||
www
|
||||
x
|
||||
y
|
||||
yes
|
||||
yet
|
||||
you
|
||||
youd
|
||||
you'll
|
||||
youll
|
||||
your
|
||||
youre
|
||||
yours
|
||||
yourself
|
||||
yourselves
|
||||
you've
|
||||
youve
|
||||
z
|
||||
zero
|
223
tools/docs/angular.io-package/index.js
Normal file
223
tools/docs/angular.io-package/index.js
Normal file
@ -0,0 +1,223 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const Package = require('dgeni').Package;
|
||||
|
||||
const jsdocPackage = require('dgeni-packages/jsdoc');
|
||||
const nunjucksPackage = require('dgeni-packages/nunjucks');
|
||||
const typescriptPackage = require('dgeni-packages/typescript');
|
||||
const gitPackage = require('dgeni-packages/git');
|
||||
const linksPackage = require('../links-package');
|
||||
const examplesPackage = require('../examples-package');
|
||||
const targetPackage = require('../target-package');
|
||||
const cheatsheetPackage = require('../cheatsheet-package');
|
||||
|
||||
const PROJECT_ROOT = path.resolve(__dirname, '../../..');
|
||||
const API_SOURCE_PATH = path.resolve(PROJECT_ROOT, 'modules');
|
||||
const CONTENTS_PATH = path.resolve(PROJECT_ROOT, 'docs/content');
|
||||
const TEMPLATES_PATH = path.resolve(PROJECT_ROOT, 'docs/templates');
|
||||
|
||||
module.exports =
|
||||
new Package(
|
||||
'angular.io',
|
||||
[
|
||||
jsdocPackage, nunjucksPackage, typescriptPackage, linksPackage, examplesPackage,
|
||||
gitPackage, targetPackage, cheatsheetPackage
|
||||
])
|
||||
|
||||
// Register the processors
|
||||
.processor(require('./processors/convertPrivateClassesToInterfaces'))
|
||||
.processor(require('./processors/generateNavigationDoc'))
|
||||
.processor(require('./processors/generateKeywords'))
|
||||
.processor(require('./processors/extractTitleFromGuides'))
|
||||
.processor(require('./processors/createOverviewDump'))
|
||||
.processor(require('./processors/checkUnbalancedBackTicks'))
|
||||
.processor(require('./processors/addNotYetDocumentedProperty'))
|
||||
.processor(require('./processors/mergeDecoratorDocs'))
|
||||
.processor(require('./processors/extractDecoratedClasses'))
|
||||
.processor(require('./processors/matchUpDirectiveDecorators'))
|
||||
.processor(require('./processors/filterMemberDocs'))
|
||||
|
||||
.config(function(checkAnchorLinksProcessor, log) {
|
||||
// TODO: re-enable
|
||||
checkAnchorLinksProcessor.$enabled = false;
|
||||
})
|
||||
|
||||
// Where do we get the source files?
|
||||
.config(function(
|
||||
readTypeScriptModules, readFilesProcessor, collectExamples, generateKeywordsProcessor) {
|
||||
|
||||
// API files are typescript
|
||||
readTypeScriptModules.basePath = API_SOURCE_PATH;
|
||||
readTypeScriptModules.ignoreExportsMatching = [/^_/];
|
||||
readTypeScriptModules.hidePrivateMembers = true;
|
||||
readTypeScriptModules.sourceFiles = [
|
||||
'@angular/common/index.ts',
|
||||
'@angular/common/testing/index.ts',
|
||||
'@angular/core/index.ts',
|
||||
'@angular/core/testing/index.ts',
|
||||
'@angular/forms/index.ts',
|
||||
'@angular/http/index.ts',
|
||||
'@angular/http/testing/index.ts',
|
||||
'@angular/platform-browser/index.ts',
|
||||
'@angular/platform-browser/testing/index.ts',
|
||||
'@angular/platform-browser-dynamic/index.ts',
|
||||
'@angular/platform-browser-dynamic/testing/index.ts',
|
||||
'@angular/platform-server/index.ts',
|
||||
'@angular/platform-server/testing/index.ts',
|
||||
'@angular/platform-webworker/index.ts',
|
||||
'@angular/platform-webworker-dynamic/index.ts',
|
||||
'@angular/router/index.ts',
|
||||
'@angular/router/testing/index.ts',
|
||||
'@angular/upgrade/index.ts',
|
||||
'@angular/upgrade/static.ts',
|
||||
];
|
||||
|
||||
readFilesProcessor.basePath = PROJECT_ROOT;
|
||||
readFilesProcessor.sourceFiles = [
|
||||
{basePath: CONTENTS_PATH, include: CONTENTS_PATH + '/cheatsheet/*.md'}, {
|
||||
basePath: API_SOURCE_PATH,
|
||||
include: API_SOURCE_PATH + '/@angular/examples/**/*',
|
||||
fileReader: 'exampleFileReader'
|
||||
}
|
||||
];
|
||||
|
||||
collectExamples.exampleFolders = ['@angular/examples'];
|
||||
|
||||
generateKeywordsProcessor.ignoreWordsFile = 'tools/docs/angular.io-package/ignore.words';
|
||||
})
|
||||
|
||||
|
||||
|
||||
// Where do we write the output files?
|
||||
.config(function(writeFilesProcessor) { writeFilesProcessor.outputFolder = 'dist/docs'; })
|
||||
|
||||
|
||||
// Target environments
|
||||
.config(function(targetEnvironments) {
|
||||
const ALLOWED_LANGUAGES = ['ts', 'js', 'dart'];
|
||||
const TARGET_LANGUAGE = 'ts';
|
||||
|
||||
ALLOWED_LANGUAGES.forEach(target => targetEnvironments.addAllowed(target));
|
||||
targetEnvironments.activate(TARGET_LANGUAGE);
|
||||
|
||||
// TODO: we may need to do something with `linkDocsInlineTagDef`
|
||||
})
|
||||
|
||||
|
||||
// Configure jsdoc-style tag parsing
|
||||
.config(function(parseTagsProcessor, getInjectables) {
|
||||
// Load up all the tag definitions in the tag-defs folder
|
||||
parseTagsProcessor.tagDefinitions =
|
||||
parseTagsProcessor.tagDefinitions.concat(getInjectables(requireFolder('./tag-defs')));
|
||||
|
||||
// We actually don't want to parse param docs in this package as we are getting the data
|
||||
// out using TS
|
||||
// TODO: rewire the param docs to the params extracted from TS
|
||||
parseTagsProcessor.tagDefinitions.forEach(function(tagDef) {
|
||||
if (tagDef.name === 'param') {
|
||||
tagDef.docProperty = 'paramData';
|
||||
tagDef.transforms = [];
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
|
||||
// Configure nunjucks rendering of docs via templates
|
||||
.config(function(
|
||||
renderDocsProcessor, versionInfo, templateFinder, templateEngine, getInjectables) {
|
||||
|
||||
// Where to find the templates for the doc rendering
|
||||
templateFinder.templateFolders = [TEMPLATES_PATH];
|
||||
// templateFinder.templateFolders.unshift(TEMPLATES_PATH);
|
||||
|
||||
// Standard patterns for matching docs to templates
|
||||
templateFinder.templatePatterns = [
|
||||
'${ doc.template }', '${ doc.id }.${ doc.docType }.template.html',
|
||||
'${ doc.id }.template.html', '${ doc.docType }.template.html',
|
||||
'${ doc.id }.${ doc.docType }.template.js', '${ doc.id }.template.js',
|
||||
'${ doc.docType }.template.js', '${ doc.id }.${ doc.docType }.template.json',
|
||||
'${ doc.id }.template.json', '${ doc.docType }.template.json', 'common.template.html'
|
||||
];
|
||||
|
||||
// Nunjucks and Angular conflict in their template bindings so change Nunjucks
|
||||
templateEngine.config.tags = {variableStart: '{$', variableEnd: '$}'};
|
||||
|
||||
templateEngine.filters =
|
||||
templateEngine.filters.concat(getInjectables(requireFolder('./rendering')));
|
||||
|
||||
// Add the version data to the renderer, for use in things like github links
|
||||
renderDocsProcessor.extraData.versionInfo = versionInfo;
|
||||
|
||||
// helpers are made available to the nunjucks templates
|
||||
renderDocsProcessor.helpers.relativePath = function(from, to) {
|
||||
return path.relative(from, to);
|
||||
};
|
||||
})
|
||||
|
||||
|
||||
|
||||
// We are going to be relaxed about ambigous links
|
||||
.config(function(getLinkInfo) {
|
||||
getLinkInfo.useFirstAmbiguousLink = false;
|
||||
// TODO: I think we don't need this for Igor's shell app
|
||||
// getLinkInfo.relativeLinks = true;
|
||||
})
|
||||
|
||||
|
||||
|
||||
.config(function(
|
||||
computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES, generateNavigationDoc,
|
||||
generateKeywordsProcessor) {
|
||||
|
||||
const API_SEGMENT = 'api';
|
||||
const GUIDE_SEGMENT = 'guide';
|
||||
const APP_SEGMENT = 'app';
|
||||
|
||||
generateNavigationDoc.outputFolder = APP_SEGMENT;
|
||||
generateKeywordsProcessor.outputFolder = APP_SEGMENT;
|
||||
|
||||
// Replace any path templates inherited from other packages
|
||||
// (we want full and transparent control)
|
||||
computePathsProcessor.pathTemplates = [
|
||||
{
|
||||
docTypes: ['module'],
|
||||
getPath: function computeModulePath(doc) {
|
||||
doc.moduleFolder =
|
||||
doc.id.replace(/^@angular\//, API_SEGMENT + '/').replace(/\/index$/, '');
|
||||
return doc.moduleFolder;
|
||||
},
|
||||
outputPathTemplate: '${moduleFolder}/index.html'
|
||||
},
|
||||
{
|
||||
docTypes: EXPORT_DOC_TYPES.concat(['decorator', 'directive', 'pipe']),
|
||||
pathTemplate: '${moduleDoc.moduleFolder}/${name}',
|
||||
outputPathTemplate: '${moduleDoc.moduleFolder}/${name}.html',
|
||||
},
|
||||
{
|
||||
docTypes: ['api-list-data', 'api-list-audit'],
|
||||
pathTemplate: APP_SEGMENT + '/${docType}.json',
|
||||
outputPathTemplate: '${path}'
|
||||
},
|
||||
{
|
||||
docTypes: ['cheatsheet-data'],
|
||||
pathTemplate: GUIDE_SEGMENT + '/cheatsheet.json',
|
||||
outputPathTemplate: '${path}'
|
||||
},
|
||||
{docTypes: ['example-region'], getOutputPath: function() {}}
|
||||
];
|
||||
});
|
||||
|
||||
function requireFolder(folderPath) {
|
||||
const absolutePath = path.resolve(__dirname, folderPath);
|
||||
return fs.readdirSync(absolutePath)
|
||||
.filter(p => !/[._]spec\.js$/.test(p)) // ignore spec files
|
||||
.map(p => require(path.resolve(absolutePath, p)));
|
||||
}
|
9
tools/docs/angular.io-package/mocks/importedSrc.ts
Normal file
9
tools/docs/angular.io-package/mocks/importedSrc.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export const x = 100;
|
42
tools/docs/angular.io-package/mocks/testSrc.ts
Normal file
42
tools/docs/angular.io-package/mocks/testSrc.ts
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* This is the module description
|
||||
*/
|
||||
|
||||
export * from './importedSrc';
|
||||
|
||||
/**
|
||||
* This is some random other comment
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is MyClass
|
||||
*/
|
||||
export class MyClass {
|
||||
message: String;
|
||||
|
||||
/**
|
||||
* Create a new MyClass
|
||||
* @param {String} name The name to say hello to
|
||||
*/
|
||||
constructor(name) { this.message = 'hello ' + name; }
|
||||
|
||||
/**
|
||||
* Return a greeting message
|
||||
*/
|
||||
greet() { return this.message; }
|
||||
}
|
||||
|
||||
/**
|
||||
* An exported function
|
||||
*/
|
||||
export const myFn = (val: number) => val * 2;
|
@ -0,0 +1,37 @@
|
||||
module.exports = function addNotYetDocumentedProperty(EXPORT_DOC_TYPES, log, createDocMessage) {
|
||||
return {
|
||||
$runAfter: ['tags-parsed'],
|
||||
$runBefore: ['rendering-docs'],
|
||||
$process: function(docs) {
|
||||
docs.forEach(function(doc) {
|
||||
|
||||
if (EXPORT_DOC_TYPES.indexOf(doc.docType) === -1) return;
|
||||
|
||||
// NotYetDocumented means that no top level comments and no member level comments
|
||||
doc.notYetDocumented = notYetDocumented(doc);
|
||||
|
||||
if (doc.constructorDoc) {
|
||||
doc.constructorDoc.notYetDocumented = notYetDocumented(doc.constructorDoc);
|
||||
doc.notYetDocumented = doc.notYetDocumented && doc.constructorDoc.notYetDocumented;
|
||||
}
|
||||
|
||||
if (doc.members) {
|
||||
doc.members.forEach(function(member) {
|
||||
member.notYetDocumented = notYetDocumented(member);
|
||||
doc.notYetDocumented = doc.notYetDocumented && member.notYetDocumented;
|
||||
});
|
||||
}
|
||||
|
||||
if (doc.notYetDocumented) {
|
||||
log.debug(createDocMessage('Not yet documented', doc));
|
||||
}
|
||||
});
|
||||
|
||||
return docs;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function notYetDocumented(doc) {
|
||||
return !doc.noDescription && doc.description.trim().length == 0;
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('addNotYetDocumentedProperty', function() {
|
||||
var dgeni, injector, processor, log;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('angular.io-package')]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('addNotYetDocumentedProperty');
|
||||
log = injector.get('log');
|
||||
});
|
||||
|
||||
it('should mark export docs with no description as "not yet documented"', function() {
|
||||
var a, b, c, d, a1, b1, c1, d1;
|
||||
var docs = [
|
||||
a = {id: 'a', docType: 'interface', description: 'some content'},
|
||||
b = {id: 'b', docType: 'class', description: 'some content'},
|
||||
c = {id: 'c', docType: 'var', description: 'some content'},
|
||||
d = {id: 'd', docType: 'function', description: 'some content'},
|
||||
a1 = {id: 'a1', docType: 'interface', description: ''},
|
||||
b1 = {id: 'b1', docType: 'class', description: ''},
|
||||
c1 = {id: 'c1', docType: 'var', description: ''},
|
||||
d1 = {id: 'd1', docType: 'function', description: ''}
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(a.notYetDocumented).toBeFalsy();
|
||||
expect(b.notYetDocumented).toBeFalsy();
|
||||
expect(c.notYetDocumented).toBeFalsy();
|
||||
expect(d.notYetDocumented).toBeFalsy();
|
||||
|
||||
expect(a1.notYetDocumented).toBeTruthy();
|
||||
expect(b1.notYetDocumented).toBeTruthy();
|
||||
expect(c1.notYetDocumented).toBeTruthy();
|
||||
expect(d1.notYetDocumented).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should mark member docs with no description as "not yet documented"', function() {
|
||||
var a, a1, a2, b, b1, b2, c, c1, c2;
|
||||
var docs = [
|
||||
a = {
|
||||
id: 'a',
|
||||
docType: 'interface',
|
||||
description: 'some content',
|
||||
members: [a1 = {id: 'a1', description: 'some content'}, a2 = {id: 'a2', description: ''}]
|
||||
},
|
||||
b = {
|
||||
id: 'b',
|
||||
docType: 'class',
|
||||
description: '',
|
||||
members: [b1 = {id: 'b1', description: 'some content'}, b2 = {id: 'b2', description: ''}]
|
||||
},
|
||||
c = {
|
||||
id: 'c',
|
||||
docType: 'class',
|
||||
description: '',
|
||||
members: [c1 = {id: 'c1', description: ''}, c2 = {id: 'c2', description: ''}]
|
||||
},
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(a.notYetDocumented).toBeFalsy();
|
||||
expect(b.notYetDocumented).toBeFalsy();
|
||||
expect(c.notYetDocumented).toBeTruthy();
|
||||
|
||||
expect(a1.notYetDocumented).toBeFalsy();
|
||||
expect(a2.notYetDocumented).toBeTruthy();
|
||||
expect(b1.notYetDocumented).toBeFalsy();
|
||||
expect(b2.notYetDocumented).toBeTruthy();
|
||||
expect(c1.notYetDocumented).toBeTruthy();
|
||||
expect(c2.notYetDocumented).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
it('should mark constructor doc with no description as "not yet documented"', function() {
|
||||
var a, a1, b, b1;
|
||||
var docs = [
|
||||
a = {
|
||||
id: 'a',
|
||||
docType: 'interface',
|
||||
description: '',
|
||||
constructorDoc: a1 = {id: 'a1', description: 'some content'}
|
||||
},
|
||||
b = {
|
||||
id: 'b',
|
||||
docType: 'interface',
|
||||
description: '',
|
||||
constructorDoc: b1 = {id: 'b1', description: ''}
|
||||
}
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(a.notYetDocumented).toBeFalsy();
|
||||
expect(b.notYetDocumented).toBeTruthy();
|
||||
|
||||
expect(a1.notYetDocumented).toBeFalsy();
|
||||
expect(b1.notYetDocumented).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
it('should not mark documents explicity tagged as `@noDescription`', function() {
|
||||
var a, a1, a2, b, b1, b2, c, c1, c2;
|
||||
var docs = [
|
||||
a = {
|
||||
id: 'a',
|
||||
docType: 'interface',
|
||||
description: 'some content',
|
||||
members: [
|
||||
a1 = {id: 'a1', description: 'some content'},
|
||||
a2 = {id: 'a2', description: '', noDescription: true}
|
||||
]
|
||||
},
|
||||
b = {
|
||||
id: 'b',
|
||||
docType: 'class',
|
||||
description: '',
|
||||
members: [
|
||||
b1 = {id: 'b1', description: 'some content'},
|
||||
b2 = {id: 'b2', description: '', noDescription: true}
|
||||
]
|
||||
},
|
||||
c = {
|
||||
id: 'c',
|
||||
docType: 'class',
|
||||
description: '',
|
||||
noDescription: true,
|
||||
members: [c1 = {id: 'c1', description: ''}, c2 = {id: 'c2', description: ''}]
|
||||
},
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(a.notYetDocumented).toBeFalsy();
|
||||
expect(b.notYetDocumented).toBeFalsy();
|
||||
expect(c.notYetDocumented).toBeFalsy();
|
||||
|
||||
expect(a1.notYetDocumented).toBeFalsy();
|
||||
expect(a2.notYetDocumented).toBeFalsy();
|
||||
expect(b1.notYetDocumented).toBeFalsy();
|
||||
expect(b2.notYetDocumented).toBeFalsy();
|
||||
expect(c1.notYetDocumented).toBeTruthy();
|
||||
expect(c2.notYetDocumented).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,33 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* @dgProcessor checkUnbalancedBackTicks
|
||||
* @description
|
||||
* Searches the rendered content for an odd number of (```) backticks,
|
||||
* which would indicate an unbalanced pair and potentially a typo in the
|
||||
* source content.
|
||||
*/
|
||||
module.exports = function checkUnbalancedBackTicks(log, createDocMessage) {
|
||||
|
||||
var BACKTICK_REGEX = /^ *```/gm;
|
||||
|
||||
return {
|
||||
// $runAfter: ['checkAnchorLinksProcessor'],
|
||||
$runAfter: ['inlineTagProcessor'],
|
||||
$runBefore: ['writeFilesProcessor'],
|
||||
$process: function(docs) {
|
||||
_.forEach(docs, function(doc) {
|
||||
if (doc.renderedContent) {
|
||||
var matches = doc.renderedContent.match(BACKTICK_REGEX);
|
||||
if (matches && matches.length % 2 !== 0) {
|
||||
doc.unbalancedBackTicks = true;
|
||||
log.warn(createDocMessage(
|
||||
'checkUnbalancedBackTicks processor: unbalanced backticks found in rendered content',
|
||||
doc));
|
||||
log.warn(doc.renderedContent);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
@ -0,0 +1,30 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
var path = require('canonical-path');
|
||||
|
||||
describe('checkUnbalancedBackTicks', function() {
|
||||
var dgeni, injector, processor, log;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('angular.io-package')]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('checkUnbalancedBackTicks');
|
||||
log = injector.get('log');
|
||||
});
|
||||
|
||||
it('should warn if there are an odd number of back ticks in the rendered content', function() {
|
||||
var docs = [{
|
||||
renderedContent: '```\n' +
|
||||
'code block\n' +
|
||||
'```\n' +
|
||||
'```\n' +
|
||||
'code block with missing closing back ticks\n'
|
||||
}];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(log.warn).toHaveBeenCalledWith(
|
||||
'checkUnbalancedBackTicks processor: unbalanced backticks found in rendered content - doc');
|
||||
expect(docs[0].unbalancedBackTicks).toBe(true);
|
||||
});
|
||||
});
|
@ -0,0 +1,11 @@
|
||||
module.exports = function convertPrivateClassesToInterfacesProcessor(
|
||||
convertPrivateClassesToInterfaces) {
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
$process: function(docs) {
|
||||
convertPrivateClassesToInterfaces(docs, false);
|
||||
return docs;
|
||||
}
|
||||
};
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function createOverviewDump() {
|
||||
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
$process: function(docs) {
|
||||
var overviewDoc = {
|
||||
id: 'overview-dump',
|
||||
aliases: ['overview-dump'],
|
||||
path: 'overview-dump',
|
||||
outputPath: 'overview-dump.html',
|
||||
modules: []
|
||||
};
|
||||
_.forEach(docs, function(doc) {
|
||||
if (doc.docType === 'module') {
|
||||
overviewDoc.modules.push(doc);
|
||||
}
|
||||
});
|
||||
docs.push(overviewDoc);
|
||||
}
|
||||
};
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function extractDecoratedClassesProcessor(EXPORT_DOC_TYPES) {
|
||||
|
||||
// Add the "directive" docType into those that can be exported from a module
|
||||
EXPORT_DOC_TYPES.push('directive', 'pipe');
|
||||
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
decoratorTypes: ['Directive', 'Component', 'Pipe'],
|
||||
$process: function(docs) {
|
||||
var decoratorTypes = this.decoratorTypes;
|
||||
|
||||
_.forEach(docs, function(doc) {
|
||||
|
||||
_.forEach(doc.decorators, function(decorator) {
|
||||
|
||||
if (decoratorTypes.indexOf(decorator.name) !== -1) {
|
||||
doc.docType = decorator.name.toLowerCase();
|
||||
doc[doc.docType + 'Options'] = decorator.argumentInfo[0];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return docs;
|
||||
}
|
||||
};
|
||||
};
|
@ -0,0 +1,48 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('extractDecoratedClasses processor', function() {
|
||||
var dgeni, injector, processor;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('angular.io-package')]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('extractDecoratedClassesProcessor');
|
||||
});
|
||||
|
||||
it('should extract specified decorator arguments', function() {
|
||||
var doc1 = {
|
||||
id: '@angular/common/ngFor',
|
||||
name: 'ngFor',
|
||||
docType: 'class',
|
||||
decorators: [{
|
||||
name: 'Directive',
|
||||
arguments: ['{selector: \'[ng-for][ng-for-of]\', properties: [\'ngForOf\']}'],
|
||||
argumentInfo: [{selector: '[ng-for][ng-for-of]', properties: ['ngForOf']}]
|
||||
}]
|
||||
};
|
||||
var doc2 = {
|
||||
id: '@angular/core/DecimalPipe',
|
||||
name: 'DecimalPipe',
|
||||
docType: 'class',
|
||||
decorators:
|
||||
[{name: 'Pipe', arguments: ['{name: \'number\'}'], argumentInfo: [{name: 'number'}]}]
|
||||
};
|
||||
|
||||
processor.$process([doc1, doc2]);
|
||||
|
||||
expect(doc1).toEqual(jasmine.objectContaining({
|
||||
id: '@angular/common/ngFor',
|
||||
name: 'ngFor',
|
||||
docType: 'directive',
|
||||
directiveOptions: {selector: '[ng-for][ng-for-of]', properties: ['ngForOf']}
|
||||
}));
|
||||
|
||||
expect(doc2).toEqual(jasmine.objectContaining({
|
||||
id: '@angular/core/DecimalPipe',
|
||||
name: 'DecimalPipe',
|
||||
docType: 'pipe',
|
||||
pipeOptions: {name: 'number'}
|
||||
}));
|
||||
});
|
||||
});
|
@ -0,0 +1,24 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function extractTitleFromGuides() {
|
||||
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
$process: function(docs) {
|
||||
_(docs).forEach(function(doc) {
|
||||
if (doc.docType === 'guide') {
|
||||
doc.name = doc.name || getNameFromHeading(doc.description);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
function getNameFromHeading(text) {
|
||||
var match = /^\s*#\s*(.*)/.exec(text);
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
module.exports = function filterMemberDocs() {
|
||||
return {
|
||||
$runAfter: ['extra-docs-added'], $runBefore: ['computing-paths'], $process: function(docs) {
|
||||
return docs.filter(function(doc) { return doc.docType !== 'member'; });
|
||||
}
|
||||
}
|
||||
};
|
139
tools/docs/angular.io-package/processors/generateKeywords.js
Normal file
139
tools/docs/angular.io-package/processors/generateKeywords.js
Normal file
@ -0,0 +1,139 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('canonical-path');
|
||||
|
||||
/**
|
||||
* @dgProcessor generateKeywordsProcessor
|
||||
* @description
|
||||
* This processor extracts all the keywords from each document and creates
|
||||
* a new document that will be rendered as a JavaScript file containing all
|
||||
* this data.
|
||||
*/
|
||||
module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
|
||||
return {
|
||||
ignoreWordsFile: undefined,
|
||||
propertiesToIgnore: [],
|
||||
docTypesToIgnore: [],
|
||||
outputFolder: '',
|
||||
$validate: {
|
||||
ignoreWordsFile: {},
|
||||
docTypesToIgnore: {},
|
||||
propertiesToIgnore: {},
|
||||
outputFolder: {presence: true}
|
||||
},
|
||||
$runAfter: ['paths-computed'],
|
||||
$runBefore: ['rendering-docs'],
|
||||
$process: function(docs) {
|
||||
|
||||
// Keywords to ignore
|
||||
var wordsToIgnore = [];
|
||||
var propertiesToIgnore;
|
||||
var docTypesToIgnore;
|
||||
|
||||
// Keywords start with "ng:" or one of $, _ or a letter
|
||||
var KEYWORD_REGEX = /^((ng:|[$_a-z])[\w\-_]+)/;
|
||||
|
||||
// Load up the keywords to ignore, if specified in the config
|
||||
if (this.ignoreWordsFile) {
|
||||
var ignoreWordsPath = path.resolve(readFilesProcessor.basePath, this.ignoreWordsFile);
|
||||
wordsToIgnore = fs.readFileSync(ignoreWordsPath, 'utf8').toString().split(/[,\s\n\r]+/gm);
|
||||
|
||||
log.debug('Loaded ignore words from "' + ignoreWordsPath + '"');
|
||||
log.silly(wordsToIgnore);
|
||||
}
|
||||
|
||||
propertiesToIgnore = convertToMap(this.propertiesToIgnore);
|
||||
log.debug('Properties to ignore', propertiesToIgnore);
|
||||
docTypesToIgnore = convertToMap(this.docTypesToIgnore);
|
||||
log.debug('Doc types to ignore', docTypesToIgnore);
|
||||
|
||||
var ignoreWordsMap = convertToMap(wordsToIgnore);
|
||||
|
||||
// If the title contains a name starting with ng, e.g. "ngController", then add the module
|
||||
// name
|
||||
// without the ng to the title text, e.g. "controller".
|
||||
function extractTitleWords(title) {
|
||||
var match = /ng([A-Z]\w*)/.exec(title);
|
||||
if (match) {
|
||||
title = title + ' ' + match[1].toLowerCase();
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
function extractWords(text, words, keywordMap) {
|
||||
var tokens = text.toLowerCase().split(/[.\s,`'"#]+/mg);
|
||||
tokens.forEach(function(token) {
|
||||
var match = token.match(KEYWORD_REGEX);
|
||||
if (match) {
|
||||
var key = match[1];
|
||||
if (!keywordMap[key]) {
|
||||
keywordMap[key] = true;
|
||||
words.push(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// We are only interested in docs that live in the right area
|
||||
const filteredDocs = docs.filter(function(doc) { return !docTypesToIgnore[doc.docType]; });
|
||||
|
||||
filteredDocs.forEach(function(doc) {
|
||||
|
||||
|
||||
var words = [];
|
||||
var keywordMap = Object.assign({}, ignoreWordsMap);
|
||||
var members = [];
|
||||
var membersMap = {};
|
||||
|
||||
// Search each top level property of the document for search terms
|
||||
Object.keys(doc).forEach(function(key) {
|
||||
const value = doc[key];
|
||||
|
||||
if (isString(value) && !propertiesToIgnore[key]) {
|
||||
extractWords(value, words, keywordMap);
|
||||
}
|
||||
|
||||
if (key === 'methods' || key === 'properties' || key === 'events') {
|
||||
value.forEach(function(member) { extractWords(member.name, members, membersMap); });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
doc.searchTerms = {
|
||||
titleWords: extractTitleWords(doc.name),
|
||||
keywords: words.sort().join(' '),
|
||||
members: members.sort().join(' ')
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
var searchData =
|
||||
filteredDocs.filter(function(page) { return page.searchTerms; }).map(function(page) {
|
||||
return Object.assign(
|
||||
{path: page.path, title: page.name, type: page.docType}, page.searchTerms);
|
||||
});
|
||||
|
||||
docs.push({
|
||||
docType: 'json-doc',
|
||||
id: 'search-data-json',
|
||||
template: 'json-doc.template.json',
|
||||
path: this.outputFolder + '/search-data.json',
|
||||
outputPath: this.outputFolder + '/search-data.json',
|
||||
data: searchData
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
function isString(value) {
|
||||
return typeof value == 'string';
|
||||
}
|
||||
|
||||
function convertToMap(collection) {
|
||||
const obj = {};
|
||||
collection.forEach(key => { obj[key] = true; });
|
||||
return obj;
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
module.exports = function generateNavigationDoc() {
|
||||
|
||||
return {
|
||||
$runAfter: ['extra-docs-added'],
|
||||
$runBefore: ['rendering-docs'],
|
||||
outputFolder: '',
|
||||
$validate: {outputFolder: {presence: true}},
|
||||
$process: function(docs) {
|
||||
var modulesDoc = {
|
||||
docType: 'data-module',
|
||||
value: {api: {sections: []}, guide: {pages: []}},
|
||||
path: this.outputFolder + '/navigation',
|
||||
outputPath: this.outputFolder + '/navigation.ts',
|
||||
serviceName: 'NAVIGATION'
|
||||
};
|
||||
|
||||
docs.forEach(function(doc) {
|
||||
if (doc.docType === 'module') {
|
||||
var moduleNavItem =
|
||||
{path: doc.path, partial: doc.outputPath, name: doc.id, type: 'module', pages: []};
|
||||
|
||||
modulesDoc.value.api.sections.push(moduleNavItem);
|
||||
|
||||
doc.exports.forEach(function(exportDoc) {
|
||||
if (!exportDoc.internal) {
|
||||
var exportNavItem = {
|
||||
path: exportDoc.path,
|
||||
partial: exportDoc.outputPath,
|
||||
name: exportDoc.name,
|
||||
type: exportDoc.docType
|
||||
};
|
||||
moduleNavItem.pages.push(exportNavItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
docs.forEach(function(doc) {
|
||||
if (doc.docType === 'guide') {
|
||||
console.log('guide', doc.name);
|
||||
var guideDoc = {path: doc.path, partial: doc.outputPath, name: doc.name, type: 'guide'};
|
||||
modulesDoc.value.guide.pages.push(guideDoc);
|
||||
}
|
||||
});
|
||||
|
||||
docs.push(modulesDoc);
|
||||
}
|
||||
};
|
||||
};
|
@ -0,0 +1,62 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* @dgProcessor
|
||||
* @description
|
||||
*
|
||||
*/
|
||||
module.exports = function matchUpDirectiveDecoratorsProcessor(aliasMap) {
|
||||
|
||||
return {
|
||||
$runAfter: ['ids-computed', 'paths-computed'],
|
||||
$runBefore: ['rendering-docs'],
|
||||
decoratorMappings: {'Inputs': 'inputs', 'Outputs': 'outputs'},
|
||||
$process: function(docs) {
|
||||
var decoratorMappings = this.decoratorMappings;
|
||||
_.forEach(docs, function(doc) {
|
||||
if (doc.docType === 'directive') {
|
||||
doc.selector = doc.directiveOptions.selector;
|
||||
|
||||
for (decoratorName in decoratorMappings) {
|
||||
var propertyName = decoratorMappings[decoratorName];
|
||||
doc[propertyName] =
|
||||
getDecoratorValues(doc.directiveOptions[propertyName], decoratorName, doc.members);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function getDecoratorValues(classDecoratorValues, memberDecoratorName, members) {
|
||||
var optionMap = {};
|
||||
var decoratorValues = {};
|
||||
|
||||
// Parse the class decorator
|
||||
_.forEach(classDecoratorValues, function(option) {
|
||||
// Options are of the form: "propName : bindingName" (bindingName is optional)
|
||||
var optionPair = option.split(':');
|
||||
var propertyName = optionPair.shift().trim();
|
||||
var bindingName = (optionPair.shift() || '').trim() || propertyName;
|
||||
|
||||
decoratorValues[propertyName] = {propertyName: propertyName, bindingName: bindingName};
|
||||
});
|
||||
|
||||
_.forEach(members, function(member) {
|
||||
_.forEach(member.decorators, function(decorator) {
|
||||
if (decorator.name === memberDecoratorName) {
|
||||
decoratorValues[member.name] = {
|
||||
propertyName: member.name,
|
||||
bindingName: decorator.arguments[0] || member.name
|
||||
};
|
||||
}
|
||||
});
|
||||
if (decoratorValues[member.name]) {
|
||||
decoratorValues[member.name].memberDoc = member;
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(decoratorValues).length) {
|
||||
return decoratorValues;
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function mergeDecoratorDocs() {
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
docsToMergeInfo: [
|
||||
{nameTemplate: _.template('${name}Decorator'), decoratorProperty: 'decoratorInterfaceDoc'}, {
|
||||
nameTemplate: _.template('${name}Metadata'),
|
||||
decoratorProperty: 'metadataDoc',
|
||||
useFields: ['howToUse', 'whatItDoes']
|
||||
},
|
||||
{nameTemplate: _.template('${name}MetadataType'), decoratorProperty: 'metadataInterfaceDoc'},
|
||||
{
|
||||
nameTemplate: _.template('${name}MetadataFactory'),
|
||||
decoratorProperty: 'metadataFactoryDoc'
|
||||
}
|
||||
],
|
||||
$process: function(docs) {
|
||||
|
||||
var docsToMergeInfo = this.docsToMergeInfo;
|
||||
var docsToMerge = Object.create(null);
|
||||
|
||||
docs.forEach(function(doc) {
|
||||
|
||||
// find all the decorators, signified by a call to `makeDecorator(metadata)`
|
||||
var makeDecorator = getMakeDecoratorCall(doc);
|
||||
if (makeDecorator) {
|
||||
doc.docType = 'decorator';
|
||||
// get the type of the decorator metadata
|
||||
doc.decoratorType = makeDecorator.arguments[0].text;
|
||||
// clear the symbol type named (e.g. ComponentMetadataFactory) since it is not needed
|
||||
doc.symbolTypeName = undefined;
|
||||
|
||||
// keep track of the docs that need to be merged into this decorator doc
|
||||
docsToMergeInfo.forEach(function(info) {
|
||||
docsToMerge[info.nameTemplate({name: doc.name})] = {
|
||||
decoratorDoc: doc,
|
||||
property: info.decoratorProperty
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// merge the metadata docs into the decorator docs
|
||||
docs = docs.filter(function(doc) {
|
||||
if (docsToMerge[doc.name]) {
|
||||
var decoratorDoc = docsToMerge[doc.name].decoratorDoc;
|
||||
var property = docsToMerge[doc.name].property;
|
||||
var useFields = docsToMerge[doc.name].useFields;
|
||||
|
||||
// attach this document to its decorator
|
||||
decoratorDoc[property] = doc;
|
||||
|
||||
// Copy over fields from the merged doc if specified
|
||||
if (useFields) {
|
||||
useFields.forEach(function(field) { decoratorDoc[field] = doc[field]; });
|
||||
}
|
||||
|
||||
// remove doc from its module doc's exports
|
||||
doc.moduleDoc.exports =
|
||||
doc.moduleDoc.exports.filter(function(exportDoc) { return exportDoc !== doc; });
|
||||
|
||||
|
||||
// remove from the overall list of docs to be rendered
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function getMakeDecoratorCall(doc, type) {
|
||||
var makeDecoratorFnName = 'make' + (type || '') + 'Decorator';
|
||||
|
||||
var initializer = doc.exportSymbol && doc.exportSymbol.valueDeclaration &&
|
||||
doc.exportSymbol.valueDeclaration.initializer;
|
||||
|
||||
if (initializer) {
|
||||
// There appear to be two forms of initializer:
|
||||
// export var Injectable: InjectableFactory =
|
||||
// <InjectableFactory>makeDecorator(InjectableMetadata);
|
||||
// and
|
||||
// export var RouteConfig: (configs: RouteDefinition[]) => ClassDecorator =
|
||||
// makeDecorator(RouteConfigAnnotation);
|
||||
// In the first case, the type assertion `<InjectableFactory>` causes the AST to contain an
|
||||
// extra level of expression
|
||||
// to hold the new type of the expression.
|
||||
if (initializer.expression && initializer.expression.expression) {
|
||||
initializer = initializer.expression;
|
||||
}
|
||||
if (initializer.expression && initializer.expression.text === makeDecoratorFnName) {
|
||||
return initializer;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('mergeDecoratorDocs processor', function() {
|
||||
var dgeni, injector, processor, decoratorDoc, otherDoc;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('angular.io-package')]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('mergeDecoratorDocs');
|
||||
|
||||
decoratorDoc = {
|
||||
name: 'X',
|
||||
docType: 'var',
|
||||
exportSymbol: {
|
||||
valueDeclaration: {
|
||||
initializer: {expression: {text: 'makeDecorator'}, arguments: [{text: 'XMetadata'}]}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
decoratorDocWithTypeAssertion = {
|
||||
name: 'Y',
|
||||
docType: 'var',
|
||||
exportSymbol: {
|
||||
valueDeclaration: {
|
||||
initializer: {
|
||||
expression: {
|
||||
type: {},
|
||||
expression: {text: 'makeDecorator'},
|
||||
arguments: [{text: 'YMetadata'}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
otherDoc = {
|
||||
name: 'Y',
|
||||
docType: 'var',
|
||||
exportSymbol: {
|
||||
valueDeclaration:
|
||||
{initializer: {expression: {text: 'otherCall'}, arguments: [{text: 'param1'}]}}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
it('should change the docType of only the docs that are initialied by a call to makeDecorator',
|
||||
function() {
|
||||
processor.$process([decoratorDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
||||
expect(decoratorDoc.docType).toEqual('decorator');
|
||||
expect(decoratorDocWithTypeAssertion.docType).toEqual('decorator');
|
||||
expect(otherDoc.docType).toEqual('var');
|
||||
});
|
||||
|
||||
it('should extract the "type" of the decorator meta data', function() {
|
||||
processor.$process([decoratorDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
||||
expect(decoratorDoc.decoratorType).toEqual('XMetadata');
|
||||
expect(decoratorDocWithTypeAssertion.decoratorType).toEqual('YMetadata');
|
||||
});
|
||||
});
|
62
tools/docs/angular.io-package/rendering/indentForMarkdown.js
Normal file
62
tools/docs/angular.io-package/rendering/indentForMarkdown.js
Normal file
@ -0,0 +1,62 @@
|
||||
module.exports = function(encodeCodeBlock) {
|
||||
// var MIXIN_PATTERN = /\S*\+\S*\(.*/;
|
||||
return {
|
||||
name: 'indentForMarkdown',
|
||||
process: function(str, width) {
|
||||
if (str == null || str.length === 0) {
|
||||
return '';
|
||||
}
|
||||
width = width || 4;
|
||||
|
||||
var lines = str.split('\n');
|
||||
var newLines = [];
|
||||
var sp = spaces(width);
|
||||
var spMixin = spaces(width - 2);
|
||||
var isAfterMarkdownTag = true;
|
||||
lines.forEach(function(line) {
|
||||
// indent lines that match mixin pattern by 2 less than specified width
|
||||
if (line.indexOf('{@example') >= 0) {
|
||||
if (isAfterMarkdownTag) {
|
||||
// happens if example follows example
|
||||
if (newLines.length > 0) {
|
||||
newLines.pop();
|
||||
} else {
|
||||
// wierd case - first expression in str is an @example
|
||||
// in this case the :marked appear above the str passed in,
|
||||
// so we need to put 'something' into the markdown tag.
|
||||
newLines.push(sp + '.'); // '.' is a dummy char
|
||||
}
|
||||
}
|
||||
newLines.push(spMixin + line);
|
||||
// after a mixin line we need to reenter markdown.
|
||||
newLines.push(spMixin + ':marked');
|
||||
isAfterMarkdownTag = true;
|
||||
} else {
|
||||
if ((!isAfterMarkdownTag) || (line.trim().length > 0)) {
|
||||
newLines.push(sp + line);
|
||||
isAfterMarkdownTag = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (isAfterMarkdownTag) {
|
||||
if (newLines.length > 0) {
|
||||
// if last line is a markdown tag remove it.
|
||||
newLines.pop();
|
||||
}
|
||||
}
|
||||
// force character to be a newLine.
|
||||
if (newLines.length > 0) newLines.push('');
|
||||
var res = newLines.join('\n');
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
function spaces(n) {
|
||||
var str = '';
|
||||
for (var i = 0; i < n; i++) {
|
||||
str += ' ';
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
};
|
6
tools/docs/angular.io-package/rendering/toId.js
Normal file
6
tools/docs/angular.io-package/rendering/toId.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = function toId() {
|
||||
return {
|
||||
name: 'toId',
|
||||
process: function(str) { return str.replace(/[^(a-z)(A-Z)(0-9)._-]/g, '-'); }
|
||||
};
|
||||
};
|
14
tools/docs/angular.io-package/rendering/toId.spec.js
Normal file
14
tools/docs/angular.io-package/rendering/toId.spec.js
Normal file
@ -0,0 +1,14 @@
|
||||
var factory = require('./toId');
|
||||
|
||||
describe('toId filter', function() {
|
||||
var filter;
|
||||
|
||||
beforeEach(function() { filter = factory(); });
|
||||
|
||||
it('should be called "toId"', function() { expect(filter.name).toEqual('toId'); });
|
||||
|
||||
it('should convert a string to make it appropriate for use as an HTML id', function() {
|
||||
expect(filter.process('This is a big string with €bad#characaters¢\nAnd even NewLines'))
|
||||
.toEqual('This-is-a-big-string-with--bad-characaters--And-even-NewLines');
|
||||
});
|
||||
});
|
15
tools/docs/angular.io-package/rendering/trimBlankLines.js
Normal file
15
tools/docs/angular.io-package/rendering/trimBlankLines.js
Normal file
@ -0,0 +1,15 @@
|
||||
module.exports = function() {
|
||||
return {
|
||||
name: 'trimBlankLines',
|
||||
process: function(str) {
|
||||
var lines = str.split(/\r?\n/);
|
||||
while (lines.length && (lines[0].trim() === '')) {
|
||||
lines.shift();
|
||||
}
|
||||
while (lines.length && (lines[lines.length - 1].trim() === '')) {
|
||||
lines.pop();
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
};
|
||||
};
|
@ -0,0 +1,15 @@
|
||||
var factory = require('./trimBlankLines');
|
||||
|
||||
describe('trimBlankLines filter', function() {
|
||||
var filter;
|
||||
|
||||
beforeEach(function() { filter = factory(); });
|
||||
|
||||
it('should be called "trimBlankLines"',
|
||||
function() { expect(filter.name).toEqual('trimBlankLines'); });
|
||||
|
||||
it('should remove empty lines from the start and end of the string', function() {
|
||||
expect(filter.process('\n \n\nsome text\n \nmore text\n \n'))
|
||||
.toEqual('some text\n \nmore text');
|
||||
});
|
||||
});
|
4
tools/docs/angular.io-package/tag-defs/Annotation.js
Normal file
4
tools/docs/angular.io-package/tag-defs/Annotation.js
Normal file
@ -0,0 +1,4 @@
|
||||
// A ts2dart compiler annotation that can be ignored in API docs.
|
||||
module.exports = function() {
|
||||
return {name: 'Annotation', ignore: true};
|
||||
};
|
3
tools/docs/angular.io-package/tag-defs/deprecated.js
Normal file
3
tools/docs/angular.io-package/tag-defs/deprecated.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'deprecated'};
|
||||
};
|
@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'docsNotRequired'};
|
||||
};
|
3
tools/docs/angular.io-package/tag-defs/experimental.js
Normal file
3
tools/docs/angular.io-package/tag-defs/experimental.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'experimental'};
|
||||
};
|
3
tools/docs/angular.io-package/tag-defs/howToUse.js
Normal file
3
tools/docs/angular.io-package/tag-defs/howToUse.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'howToUse'};
|
||||
};
|
5
tools/docs/angular.io-package/tag-defs/internal.js
Normal file
5
tools/docs/angular.io-package/tag-defs/internal.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = function() {
|
||||
return {
|
||||
name: 'internal', transforms: function() { return true; }
|
||||
}
|
||||
};
|
3
tools/docs/angular.io-package/tag-defs/ngModule.js
Normal file
3
tools/docs/angular.io-package/tag-defs/ngModule.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'ngModule'};
|
||||
};
|
3
tools/docs/angular.io-package/tag-defs/no-description.js
Normal file
3
tools/docs/angular.io-package/tag-defs/no-description.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'noDescription', transforms: function() { return true; }};
|
||||
};
|
3
tools/docs/angular.io-package/tag-defs/security.js
Normal file
3
tools/docs/angular.io-package/tag-defs/security.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'security'};
|
||||
};
|
3
tools/docs/angular.io-package/tag-defs/stable.js
Normal file
3
tools/docs/angular.io-package/tag-defs/stable.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'stable'};
|
||||
};
|
3
tools/docs/angular.io-package/tag-defs/syntax.js
Normal file
3
tools/docs/angular.io-package/tag-defs/syntax.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'syntax'};
|
||||
};
|
4
tools/docs/angular.io-package/tag-defs/ts2dart_const.js
Normal file
4
tools/docs/angular.io-package/tag-defs/ts2dart_const.js
Normal file
@ -0,0 +1,4 @@
|
||||
// A ts2dart compiler annotation that can be ignored in API docs.
|
||||
module.exports = function() {
|
||||
return {name: 'ts2dart_const', ignore: true};
|
||||
};
|
3
tools/docs/angular.io-package/tag-defs/whatItDoes.js
Normal file
3
tools/docs/angular.io-package/tag-defs/whatItDoes.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'whatItDoes'};
|
||||
};
|
16
tools/docs/cheatsheet-package/index.js
Normal file
16
tools/docs/cheatsheet-package/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
var Package = require('dgeni').Package;
|
||||
|
||||
module.exports = new Package(
|
||||
'cheatsheet',
|
||||
[
|
||||
require('../content-package'), require('../target-package'),
|
||||
require('dgeni-packages/git'), require('dgeni-packages/nunjucks')
|
||||
])
|
||||
|
||||
.factory(require('./services/cheatsheetItemParser'))
|
||||
.processor(require('./processors/createCheatsheetDoc'))
|
||||
|
||||
.config(function(parseTagsProcessor, getInjectables) {
|
||||
parseTagsProcessor.tagDefinitions = parseTagsProcessor.tagDefinitions.concat(
|
||||
getInjectables(require('./tag-defs')));
|
||||
});
|
@ -0,0 +1,48 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function createCheatsheetDoc(
|
||||
createDocMessage, renderMarkdown, versionInfo, targetEnvironments) {
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
$process: function(docs) {
|
||||
|
||||
var currentEnvironment = targetEnvironments.isActive('ts') && 'TypeScript' ||
|
||||
targetEnvironments.isActive('js') && 'JavaScript' ||
|
||||
targetEnvironments.isActive('dart') && 'Dart';
|
||||
|
||||
var cheatsheetDoc = {
|
||||
id: 'cheatsheet',
|
||||
aliases: ['cheatsheet'],
|
||||
docType: 'cheatsheet-data',
|
||||
sections: [],
|
||||
version: versionInfo,
|
||||
currentEnvironment: currentEnvironment
|
||||
};
|
||||
|
||||
docs = docs.filter(function(doc) {
|
||||
if (doc.docType === 'cheatsheet-section') {
|
||||
var section = _.pick(doc, ['name', 'description', 'items', 'index']);
|
||||
|
||||
// Let's make sure that the descriptions are rendered as markdown
|
||||
section.description = renderMarkdown(section.description);
|
||||
section.items.forEach(function(item) {
|
||||
item.description = renderMarkdown(item.description);
|
||||
});
|
||||
|
||||
|
||||
cheatsheetDoc.sections.push(section);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Sort the sections by their index
|
||||
cheatsheetDoc.sections.sort(function(a, b) { return a.index - b.index; });
|
||||
|
||||
docs.push(cheatsheetDoc);
|
||||
|
||||
return docs;
|
||||
}
|
||||
};
|
||||
};
|
125
tools/docs/cheatsheet-package/services/cheatsheetItemParser.js
Normal file
125
tools/docs/cheatsheet-package/services/cheatsheetItemParser.js
Normal file
@ -0,0 +1,125 @@
|
||||
/**
|
||||
* @dgService
|
||||
* @description
|
||||
* Parse the text from a cheatsheetItem tag into a cheatsheet item object
|
||||
* The text must contain a syntax block followed by zero or more bold matchers and finally a
|
||||
* description
|
||||
* The syntax block and bold matchers must be wrapped in backticks and be separated by pipes.
|
||||
* For example
|
||||
*
|
||||
* ```
|
||||
* `<div [ng-switch]="conditionExpression">
|
||||
* <template [ng-switch-when]="case1Exp">...</template>
|
||||
* <template ng-switch-when="case2LiteralString">...</template>
|
||||
* <template ng-switch-default>...</template>
|
||||
* </div>`|`[ng-switch]`|`[ng-switch-when]`|`ng-switch-when`|`ng-switch-default`
|
||||
* Conditionally swaps the contents of the div by selecting one of the embedded templates based on
|
||||
* the current value of conditionExpression.
|
||||
* ```
|
||||
*
|
||||
* will be parsed into
|
||||
*
|
||||
* ```
|
||||
* {
|
||||
* syntax: '<div [ng-switch]="conditionExpression">\n'+
|
||||
* ' <template [ng-switch-when]="case1Exp">...</template>\n'+
|
||||
* ' <template ng-switch-when="case2LiteralString">...</template>\n'+
|
||||
* ' <template ng-switch-default>...</template>\n'+
|
||||
* '</div>',
|
||||
* bold: ['[ng-switch]', '[ng-switch-when]', 'ng-switch-when', 'ng-switch-default'],
|
||||
* description: 'Conditionally swaps the contents of the div by selecting one of the embedded
|
||||
* templates based on the current value of conditionExpression.'
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
module.exports =
|
||||
function cheatsheetItemParser(targetEnvironments) {
|
||||
return function(text) {
|
||||
var fields = getFields(text, ['syntax', 'description']);
|
||||
|
||||
var item = {syntax: '', bold: [], description: ''};
|
||||
|
||||
fields.forEach(function(field) {
|
||||
if (!field.languages || targetEnvironments.someActive(field.languages)) {
|
||||
switch (field.name) {
|
||||
case 'syntax':
|
||||
parseSyntax(field.value.trim());
|
||||
break;
|
||||
case 'description':
|
||||
item.description = field.value.trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return item;
|
||||
|
||||
function parseSyntax(text) {
|
||||
var index = 0;
|
||||
|
||||
if (text.charAt(index) !== '`') throw new Error('item syntax must start with a backtick');
|
||||
|
||||
var start = index + 1;
|
||||
index = text.indexOf('`', start);
|
||||
if (index === -1) throw new Error('item syntax must end with a backtick');
|
||||
item.syntax = text.substring(start, index);
|
||||
start = index + 1;
|
||||
|
||||
// skip to next pipe
|
||||
while (index < text.length && text.charAt(index) !== '|') index += 1;
|
||||
|
||||
while (text.charAt(start) === '|') {
|
||||
start += 1;
|
||||
|
||||
// skip whitespace
|
||||
while (start < text.length && /\s/.test(text.charAt(start))) start++;
|
||||
|
||||
if (text.charAt(start) !== '`') throw new Error('bold matcher must start with a backtick');
|
||||
|
||||
start += 1;
|
||||
index = text.indexOf('`', start);
|
||||
if (index === -1) throw new Error('bold matcher must end with a backtick');
|
||||
item.bold.push(text.substring(start, index));
|
||||
start = index + 1;
|
||||
}
|
||||
|
||||
if (start !== text.length) {
|
||||
throw new Error(
|
||||
'syntax field must only contain a syntax code block and zero or more bold ' +
|
||||
'matcher code blocks, delimited by pipes.\n' +
|
||||
'Instead it was "' + text + '"');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function getFields(text, fieldNames) {
|
||||
var FIELD_START = /^([^:(]+)\(?([^)]+)?\)?:$/;
|
||||
var lines = text.split('\n');
|
||||
var fields = [];
|
||||
var field, line;
|
||||
while (lines.length) {
|
||||
line = lines.shift();
|
||||
var match = FIELD_START.exec(line);
|
||||
if (match && fieldNames.indexOf(match[1]) !== -1) {
|
||||
// start new field
|
||||
if (field) {
|
||||
fields.push(field);
|
||||
}
|
||||
field = {name: match[1], languages: (match[2] && match[2].split(' ')), value: ''};
|
||||
} else {
|
||||
if (!field)
|
||||
throw new Error(
|
||||
'item must start with one of the following field specifiers:\n' +
|
||||
fieldNames.map(function(field) { return field + ':'; }).join('\n') + '\n' +
|
||||
'but instead it contained: "' + text + '"');
|
||||
field.value += line + '\n';
|
||||
}
|
||||
}
|
||||
if (field) {
|
||||
fields.push(field);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('cheatsheetItemParser', function() {
|
||||
var dgeni, injector, cheatsheetItemParser;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('cheatsheet-package')]);
|
||||
injector = dgeni.configureInjector();
|
||||
cheatsheetItemParser = injector.get('cheatsheetItemParser');
|
||||
var targetEnvironments = injector.get('targetEnvironments');
|
||||
targetEnvironments.addAllowed('js');
|
||||
targetEnvironments.addAllowed('ts', true);
|
||||
});
|
||||
|
||||
describe('no language targets', function() {
|
||||
it('should extract the syntax', function() {
|
||||
expect(cheatsheetItemParser('syntax:\n`abc`'))
|
||||
.toEqual({syntax: 'abc', bold: [], description: ''});
|
||||
});
|
||||
|
||||
it('should extract the bolds', function() {
|
||||
expect(cheatsheetItemParser('syntax:\n`abc`|`bold1`|`bold2`'))
|
||||
.toEqual({syntax: 'abc', bold: ['bold1', 'bold2'], description: ''});
|
||||
});
|
||||
|
||||
it('should extract the description', function() {
|
||||
expect(cheatsheetItemParser('syntax:\n`abc`|`bold1`|`bold2`\ndescription:\nsome description'))
|
||||
.toEqual({syntax: 'abc', bold: ['bold1', 'bold2'], description: 'some description'});
|
||||
});
|
||||
|
||||
it('should allow bold to be optional', function() {
|
||||
expect(cheatsheetItemParser('syntax:\n`abc`\ndescription:\nsome description'))
|
||||
.toEqual({syntax: 'abc', bold: [], description: 'some description'});
|
||||
});
|
||||
|
||||
it('should allow whitespace between the parts', function() {
|
||||
expect(cheatsheetItemParser(
|
||||
'syntax:\n`abc`| `bold1`| `bold2`\ndescription:\n\nsome description'))
|
||||
.toEqual({syntax: 'abc', bold: ['bold1', 'bold2'], description: 'some description'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with language targets', function() {
|
||||
it('should extract the active language', function() {
|
||||
expect(cheatsheetItemParser(
|
||||
'syntax(ts):\n`abc`|`bold1`|`bold2`\ndescription(ts):\nsome description'))
|
||||
.toEqual({syntax: 'abc', bold: ['bold1', 'bold2'], description: 'some description'});
|
||||
});
|
||||
|
||||
it('should ignore the non-active language', function() {
|
||||
expect(cheatsheetItemParser(
|
||||
'syntax(js):\n`abc`|`bold1`|`bold2`\ndescription(js):\nsome description'))
|
||||
.toEqual({syntax: '', bold: [], description: ''});
|
||||
});
|
||||
|
||||
it('should select the active language and ignore non-active language', function() {
|
||||
expect(cheatsheetItemParser(
|
||||
'syntax(js):\n`JS`|`boldJS``\n' +
|
||||
'syntax(ts):\n`TS`|`boldTS`\n' +
|
||||
'description(js):\nJS description\n' +
|
||||
'description(ts):\nTS description'))
|
||||
.toEqual({syntax: 'TS', bold: ['boldTS'], description: 'TS description'});
|
||||
});
|
||||
|
||||
it('should error if a language target is used that is not allowed', function() {
|
||||
expect(function() {
|
||||
cheatsheetItemParser(
|
||||
'syntax(dart):\n`abc`|`bold1`|`bold2`\ndescription(ts):\nsome description');
|
||||
})
|
||||
.toThrowError(
|
||||
'Error accessing target "dart". It is not in the list of allowed targets: js,ts');
|
||||
});
|
||||
});
|
||||
});
|
14
tools/docs/cheatsheet-package/tag-defs/cheatsheet-index.js
Normal file
14
tools/docs/cheatsheet-package/tag-defs/cheatsheet-index.js
Normal file
@ -0,0 +1,14 @@
|
||||
module.exports = function(createDocMessage) {
|
||||
return {
|
||||
name: 'cheatsheetIndex',
|
||||
docProperty: 'index',
|
||||
transforms: function(doc, tag, value) {
|
||||
try {
|
||||
return parseInt(value, 10);
|
||||
} catch (x) {
|
||||
throw new Error(
|
||||
createDocMessage('"@' + tag.tagName + '" must be followed by a number', doc));
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
15
tools/docs/cheatsheet-package/tag-defs/cheatsheet-item.js
Normal file
15
tools/docs/cheatsheet-package/tag-defs/cheatsheet-item.js
Normal file
@ -0,0 +1,15 @@
|
||||
module.exports = function(createDocMessage, cheatsheetItemParser) {
|
||||
return {
|
||||
name: 'cheatsheetItem',
|
||||
multi: true,
|
||||
docProperty: 'items',
|
||||
transforms: function(doc, tag, value) {
|
||||
try {
|
||||
return cheatsheetItemParser(value);
|
||||
} catch (x) {
|
||||
throw new Error(createDocMessage(
|
||||
'"@' + tag.tagName + '" tag has an invalid format - ' + x.message, doc));
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
10
tools/docs/cheatsheet-package/tag-defs/cheatsheet-section.js
Normal file
10
tools/docs/cheatsheet-package/tag-defs/cheatsheet-section.js
Normal file
@ -0,0 +1,10 @@
|
||||
module.exports = function() {
|
||||
return {
|
||||
name: 'cheatsheetSection',
|
||||
docProperty: 'docType',
|
||||
transforms: function(doc, tag, value) {
|
||||
doc.name = value ? value.trim() : '';
|
||||
return 'cheatsheet-section';
|
||||
}
|
||||
};
|
||||
};
|
2
tools/docs/cheatsheet-package/tag-defs/index.js
Normal file
2
tools/docs/cheatsheet-package/tag-defs/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
module.exports =
|
||||
[require('./cheatsheet-section'), require('./cheatsheet-index'), require('./cheatsheet-item')];
|
35
tools/docs/content-package/index.js
Normal file
35
tools/docs/content-package/index.js
Normal file
@ -0,0 +1,35 @@
|
||||
var Package = require('dgeni').Package;
|
||||
var jsdocPackage = require('dgeni-packages/jsdoc');
|
||||
var linksPackage = require('../links-package');
|
||||
var path = require('canonical-path');
|
||||
var fs = require('fs');
|
||||
|
||||
// Define the dgeni package for generating the docs
|
||||
module.exports = new Package('content', [jsdocPackage, linksPackage])
|
||||
|
||||
// Register the services and file readers
|
||||
.factory(require('./readers/content'))
|
||||
|
||||
// Configure file reading
|
||||
.config(function(readFilesProcessor, contentFileReader) {
|
||||
readFilesProcessor.fileReaders.push(contentFileReader);
|
||||
})
|
||||
|
||||
// Configure ids and paths
|
||||
.config(function(computeIdsProcessor, computePathsProcessor) {
|
||||
|
||||
computeIdsProcessor.idTemplates.push({
|
||||
docTypes: ['content'],
|
||||
getId: function(doc) {
|
||||
return doc.fileInfo
|
||||
.relativePath
|
||||
// path should be relative to `modules` folder
|
||||
.replace(/.*\/?modules\//, '')
|
||||
// path should not include `/docs/`
|
||||
.replace(/\/docs\//, '/')
|
||||
// path should not have a suffix
|
||||
.replace(/\.\w*$/, '');
|
||||
},
|
||||
getAliases: function(doc) { return [doc.id]; }
|
||||
});
|
||||
});
|
26
tools/docs/content-package/readers/content.js
Normal file
26
tools/docs/content-package/readers/content.js
Normal file
@ -0,0 +1,26 @@
|
||||
var path = require('canonical-path');
|
||||
|
||||
/**
|
||||
* @dgService
|
||||
* @description
|
||||
* This file reader will pull the contents from a text file (by default .md)
|
||||
*
|
||||
* The doc will initially have the form:
|
||||
* ```
|
||||
* {
|
||||
* content: 'the content of the file',
|
||||
* startingLine: 1
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
module.exports = function contentFileReader() {
|
||||
return {
|
||||
name: 'contentFileReader',
|
||||
defaultPattern: /\.md$/,
|
||||
getDocs: function(fileInfo) {
|
||||
|
||||
// We return a single element array because content files only contain one document
|
||||
return [{docType: 'guide', content: fileInfo.content, startingLine: 1}];
|
||||
}
|
||||
};
|
||||
};
|
44
tools/docs/content-package/readers/content.spec.js
Normal file
44
tools/docs/content-package/readers/content.spec.js
Normal file
@ -0,0 +1,44 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
var path = require('canonical-path');
|
||||
|
||||
describe('contentFileReader', function() {
|
||||
var dgeni, injector, fileReader;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('content-package', true)]);
|
||||
injector = dgeni.configureInjector();
|
||||
fileReader = injector.get('contentFileReader');
|
||||
});
|
||||
|
||||
var createFileInfo = function(file, content, basePath) {
|
||||
return {
|
||||
fileReader: fileReader.name,
|
||||
filePath: file,
|
||||
baseName: path.basename(file, path.extname(file)),
|
||||
extension: path.extname(file).replace(/^\./, ''),
|
||||
basePath: basePath,
|
||||
relativePath: path.relative(basePath, file),
|
||||
content: content
|
||||
};
|
||||
};
|
||||
|
||||
describe('defaultPattern', function() {
|
||||
it('should match .md files', function() {
|
||||
expect(fileReader.defaultPattern.test('abc.md')).toBeTruthy();
|
||||
expect(fileReader.defaultPattern.test('abc.js')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('getDocs', function() {
|
||||
it('should return an object containing info about the file and its contents', function() {
|
||||
var fileInfo = createFileInfo(
|
||||
'project/path/modules/someModule/foo/docs/subfolder/bar.ngdoc', 'A load of content',
|
||||
'project/path');
|
||||
expect(fileReader.getDocs(fileInfo)).toEqual([
|
||||
{docType: 'guide', content: 'A load of content', startingLine: 1}
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
11
tools/docs/eslintrc.js
Normal file
11
tools/docs/eslintrc.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
'globals': {'describe': true, 'beforeEach': true, 'it': true, 'expect': true},
|
||||
'env': {'node': true},
|
||||
'extends': 'eslint:recommended',
|
||||
'rules': {
|
||||
'indent': ['error', 2],
|
||||
'linebreak-style': ['error', 'unix'],
|
||||
'quotes': ['error', 'single'],
|
||||
'semi': ['error', 'always']
|
||||
}
|
||||
};
|
14
tools/docs/examples-package/file-readers/example-reader.js
Normal file
14
tools/docs/examples-package/file-readers/example-reader.js
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* The point of this reader is to tag all the files that are going to be used as examples in the
|
||||
* documentation.
|
||||
* Later on we can extract the regions, via "shredding"; and we can also construct runnable examples
|
||||
* for passing to plunker and the like.
|
||||
*/
|
||||
module.exports = function exampleFileReader(log) {
|
||||
return {
|
||||
name: 'exampleFileReader',
|
||||
getDocs: function(fileInfo) {
|
||||
return [{docType: 'example-file', content: fileInfo.content, startingLine: 1}];
|
||||
}
|
||||
};
|
||||
};
|
29
tools/docs/examples-package/index.js
Normal file
29
tools/docs/examples-package/index.js
Normal file
@ -0,0 +1,29 @@
|
||||
var Package = require('dgeni').Package;
|
||||
var jsdocPackage = require('dgeni-packages/jsdoc');
|
||||
|
||||
module.exports =
|
||||
new Package('examples', [jsdocPackage])
|
||||
|
||||
.factory(require('./inline-tag-defs/example'))
|
||||
// .factory(require('./inline-tag-defs/exampleTabs'))
|
||||
.factory(require('./services/parseArgString'))
|
||||
.factory(require('./services/getExampleFilename'))
|
||||
.factory(require('./services/example-map'))
|
||||
.factory(require('./file-readers/example-reader'))
|
||||
.factory(require('./services/region-parser'))
|
||||
|
||||
.processor(require('./processors/collect-examples'))
|
||||
|
||||
.config(function(readFilesProcessor, exampleFileReader) {
|
||||
readFilesProcessor.fileReaders.push(exampleFileReader);
|
||||
})
|
||||
|
||||
.config(function(inlineTagProcessor, exampleInlineTagDef) {
|
||||
inlineTagProcessor.inlineTagDefinitions.push(exampleInlineTagDef);
|
||||
// inlineTagProcessor.inlineTagDefinitions.push(exampleTabsInlineTagDef);
|
||||
})
|
||||
|
||||
.config(function(computePathsProcessor) {
|
||||
computePathsProcessor.pathTemplates.push(
|
||||
{docTypes: ['example-region'], getPath: function() {}, getOutputPath: function() {}});
|
||||
});
|
57
tools/docs/examples-package/inline-tag-defs/example.js
Normal file
57
tools/docs/examples-package/inline-tag-defs/example.js
Normal file
@ -0,0 +1,57 @@
|
||||
var path = require('canonical-path');
|
||||
var fs = require('fs');
|
||||
var entities = require('entities');
|
||||
|
||||
/**
|
||||
* @dgService exampleInlineTagDef
|
||||
* @description
|
||||
* Process inline example tags (of the form {@example relativePath region -title='some title'
|
||||
* -stylePattern='{some style pattern}' }),
|
||||
* replacing them with code from a shredded file
|
||||
* Examples:
|
||||
* {@example core/application_spec.ts hello-app -title='Sample component' }
|
||||
* {@example core/application_spec.ts -region=hello-app -title='Sample component' }
|
||||
* @kind function
|
||||
*/
|
||||
module.exports = function exampleInlineTagDef(
|
||||
parseArgString, exampleMap, getExampleFilename, createDocMessage, log, collectExamples) {
|
||||
return {
|
||||
name: 'example',
|
||||
description:
|
||||
'Process inline example tags (of the form {@example some/uri Some Title}), replacing them with HTML anchors',
|
||||
|
||||
|
||||
handler: function(doc, tagName, tagDescription) {
|
||||
const EXAMPLES_FOLDER = collectExamples.exampleFolders[0];
|
||||
|
||||
var tagArgs = parseArgString(entities.decodeHTML(tagDescription));
|
||||
|
||||
var unnamedArgs = tagArgs._;
|
||||
var relativePath = unnamedArgs[0];
|
||||
var regionName = tagArgs.region || (unnamedArgs.length > 1 ? unnamedArgs[1] : null);
|
||||
var title = tagArgs.title || (unnamedArgs.length > 2 ? unnamedArgs[2] : null);
|
||||
var stylePattern = tagArgs.stylePattern; // TODO: not yet implemented here
|
||||
|
||||
var exampleFile = exampleMap[EXAMPLES_FOLDER][relativePath];
|
||||
if (!exampleFile) {
|
||||
log.error(
|
||||
createDocMessage('Missing example file... relativePath: "' + relativePath + '".', doc));
|
||||
log.error(
|
||||
'Example files available are:', Object.keys(exampleMap[EXAMPLES_FOLDER]).join('\n'));
|
||||
return '';
|
||||
}
|
||||
|
||||
var sourceCode = exampleFile.regions[regionName];
|
||||
if (!sourceCode) {
|
||||
log.error(createDocMessage(
|
||||
'Missing example region... relativePath: "' + relativePath + '", region: "' +
|
||||
regionName + '".',
|
||||
doc));
|
||||
log.error('Regions available are:', Object.keys[exampleFile.regions]);
|
||||
return '';
|
||||
}
|
||||
|
||||
return sourceCode.renderedContent;
|
||||
}
|
||||
};
|
||||
};
|
61
tools/docs/examples-package/inline-tag-defs/exampleTabs.js
Normal file
61
tools/docs/examples-package/inline-tag-defs/exampleTabs.js
Normal file
@ -0,0 +1,61 @@
|
||||
var path = require('canonical-path');
|
||||
var fs = require('fs');
|
||||
|
||||
/**
|
||||
* @dgService exampleTabsInlineTagDef
|
||||
* @description
|
||||
* Process inline example tags (of the form {@example relativePath region -title='some title'
|
||||
* -stylePattern='{some style pattern}' }),
|
||||
* replacing them with a jade makeExample mixin call.
|
||||
* Examples:
|
||||
* {@exampleTabs core/application_spec.ts,core/application_spec.ts "hello-app,hello-app2"
|
||||
* -titles="Hello app1, Hello app2" }
|
||||
* {@exampleTabs core/application_spec.ts,core/application_spec.ts regions="hello-app,hello-app2"
|
||||
* -titles="Hello app1, Hello app2" }
|
||||
* @kind function
|
||||
*/
|
||||
module.exports = function exampleTabsInlineTagDef(
|
||||
getLinkInfo, parseArgString, createDocMessage, log) {
|
||||
return {
|
||||
name: 'exampleTabs',
|
||||
description:
|
||||
'Process inline example tags (of the form {@example some/uri Some Title}), replacing them with HTML anchors',
|
||||
handler: function(doc, tagName, tagDescription) {
|
||||
|
||||
var tagArgs = parseArgString(tagDescription);
|
||||
var unnamedArgs = tagArgs._;
|
||||
var relativePaths = unnamedArgs[0].split(',');
|
||||
var regions = tagArgs.regions || (unnamedArgs.length > 1 ? unnamedArgs[1] : null);
|
||||
var titles = tagArgs.titles || (unnamedArgs.length > 2 ? unnamedArgs[2] : null);
|
||||
if (regions) {
|
||||
regions = regions.split(',');
|
||||
}
|
||||
|
||||
// TODO: not yet implemented here
|
||||
var stylePatterns = tagArgs.stylePattern;
|
||||
|
||||
var mixinPaths = relativePaths.map(function(relativePath, ix) {
|
||||
var fragFileName = getApiFragmentFileName(relativePath, regions && regions[ix]);
|
||||
if (!fs.existsSync(fragFileName)) {
|
||||
// TODO: log.warn(createDocMessage('Invalid example (unable to locate fragment file: ' +
|
||||
// quote(fragFileName) + ")", doc));
|
||||
}
|
||||
return path.join('_api', relativePath);
|
||||
});
|
||||
|
||||
var comma = ', '
|
||||
var pathsArg = quote(mixinPaths.join(','));
|
||||
var regionsArg = regions ? quote(regions.join(',')) : 'null';
|
||||
var titlesArg = titles ? quote(titles) : 'null';
|
||||
var res = ['+makeTabs(', pathsArg, comma, regionsArg, comma, titlesArg, ')'].join('');
|
||||
return res;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
function quote(str) {
|
||||
if (str == null || str.length === 0) return str;
|
||||
str = str.replace('\'', '\'\'');
|
||||
return '\'' + str + '\'';
|
||||
}
|
67
tools/docs/examples-package/processors/collect-examples.js
Normal file
67
tools/docs/examples-package/processors/collect-examples.js
Normal file
@ -0,0 +1,67 @@
|
||||
const {mapObject} = require('../utils');
|
||||
|
||||
module.exports = function collectExamples(exampleMap, regionParser, log, createDocMessage) {
|
||||
return {
|
||||
$runAfter: ['files-read'],
|
||||
$runBefore: ['parsing-tags'],
|
||||
$validate: {exampleFolders: {presence: true}},
|
||||
$process: function(docs) {
|
||||
const exampleFolders = this.exampleFolders;
|
||||
const regionDocs = [];
|
||||
docs = docs.filter((doc) => {
|
||||
if (doc.docType === 'example-file') {
|
||||
try {
|
||||
// find the first matching folder
|
||||
exampleFolders.some((folder) => {
|
||||
if (doc.fileInfo.relativePath.indexOf(folder) === 0) {
|
||||
const relativePath =
|
||||
doc.fileInfo.relativePath.substr(folder.length).replace(/^\//, '');
|
||||
exampleMap[folder] = exampleMap[folder] || {};
|
||||
exampleMap[folder][relativePath] = doc;
|
||||
|
||||
const parsedRegions = regionParser(doc.content, doc.fileInfo.extension);
|
||||
|
||||
log.debug(
|
||||
'found example file', folder, relativePath, Object.keys(parsedRegions.regions));
|
||||
|
||||
doc.renderedContent = parsedRegions.contents;
|
||||
|
||||
// Map each region into a doc that can be put through the rendering pipeline
|
||||
doc.regions = mapObject(parsedRegions.regions, (regionName, regionContents) => {
|
||||
const regionDoc =
|
||||
createRegionDoc(folder, relativePath, regionName, regionContents);
|
||||
regionDocs.push(regionDoc);
|
||||
return regionDoc;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
} catch (e) {
|
||||
throw new Error(createDocMessage(e.message, doc, e));
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return docs.concat(regionDocs);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function createRegionDoc(folder, relativePath, regionName, regionContents) {
|
||||
const path = folder + '/' + relativePath;
|
||||
const id = path + '#' + regionName
|
||||
return {
|
||||
docType: 'example-region',
|
||||
path: path,
|
||||
name: regionName,
|
||||
id: id,
|
||||
aliases: [id],
|
||||
contents: regionContents
|
||||
};
|
||||
}
|
193
tools/docs/examples-package/processors/collect-examples.spec.js
Normal file
193
tools/docs/examples-package/processors/collect-examples.spec.js
Normal file
@ -0,0 +1,193 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
var path = require('path');
|
||||
|
||||
describe('collectExampleRegions processor', () => {
|
||||
var injector, processor, exampleMap, regionParser;
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
regionParser = jasmine.createSpy('regionParser').and.callFake(function(contents, extension) {
|
||||
return { contents: 'PARSED:' + contents, regions: {dummy: extension} }
|
||||
});
|
||||
|
||||
const dgeni =
|
||||
new Dgeni([testPackage('examples-package', true).factory('regionParser', function() {
|
||||
return regionParser;
|
||||
})]);
|
||||
|
||||
injector = dgeni.configureInjector();
|
||||
exampleMap = injector.get('exampleMap');
|
||||
processor = injector.get('collectExamples');
|
||||
|
||||
processor.exampleFolders = ['examples-1', 'examples-2'];
|
||||
});
|
||||
|
||||
it('should identify example files that are in the exampleFolders', () => {
|
||||
const docs = [
|
||||
createDoc('A', 'examples-1/x/app.js'), createDoc('B', 'examples-1/y/index.html'),
|
||||
createDoc('C', 'examples-2/s/app.js'), createDoc('D', 'examples-2/t/style.css'),
|
||||
createDoc('E', 'other/b/c.js')
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(exampleMap['examples-1']['x/app.js']).toBeDefined();
|
||||
expect(exampleMap['examples-1']['y/index.html']).toBeDefined();
|
||||
expect(exampleMap['examples-2']['s/app.js']).toBeDefined();
|
||||
expect(exampleMap['examples-2']['t/style.css']).toBeDefined();
|
||||
|
||||
expect(exampleMap['other']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should remove example files from the docs collection', () => {
|
||||
const docs = [
|
||||
createDoc('Example A', 'examples-1/x/app.js'),
|
||||
createDoc('Example B', 'examples-1/y/index.html'),
|
||||
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
|
||||
createDoc('Example C', 'examples-2/s/app.js'),
|
||||
createDoc('Other doc 2', 'other/b/c.js', 'content')
|
||||
];
|
||||
|
||||
const processedDocs = processor.$process(docs);
|
||||
|
||||
expect(processedDocs.filter(doc => doc.docType === 'example-file')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not remove docs from the docs collection that are not example files', () => {
|
||||
const docs = [
|
||||
createDoc('Example A', 'examples-1/x/app.js'),
|
||||
createDoc('Example B', 'examples-1/y/index.html'),
|
||||
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
|
||||
createDoc('Example C', 'examples-2/s/app.js'),
|
||||
createDoc('Other doc 2', 'other/b/c.js', 'content')
|
||||
];
|
||||
|
||||
const processedDocs = processor.$process(docs);
|
||||
|
||||
expect(processedDocs.filter(doc => doc.docType !== 'example-file'))
|
||||
.toEqual(jasmine.objectContaining([
|
||||
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
|
||||
createDoc('Other doc 2', 'other/b/c.js', 'content')
|
||||
]));
|
||||
});
|
||||
|
||||
it('should call `regionParser` from with the content and file extension of each example doc',
|
||||
() => {
|
||||
const docs = [
|
||||
createDoc('Example A', 'examples-1/x/app.js'),
|
||||
createDoc('Example B', 'examples-1/y/index.html'),
|
||||
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
|
||||
createDoc('Example C', 'examples-2/s/app.js'),
|
||||
createDoc('Other doc 2', 'other/b/c.js', 'content')
|
||||
];
|
||||
|
||||
const processedDocs = processor.$process(docs);
|
||||
|
||||
expect(regionParser).toHaveBeenCalledTimes(3);
|
||||
expect(regionParser).toHaveBeenCalledWith('Example A', 'js');
|
||||
expect(regionParser).toHaveBeenCalledWith('Example B', 'html');
|
||||
expect(regionParser).toHaveBeenCalledWith('Example C', 'js');
|
||||
});
|
||||
|
||||
|
||||
it('should attach parsed content as renderedContent to the example file docs', () => {
|
||||
const docs = [
|
||||
createDoc('A', 'examples-1/x/app.js'),
|
||||
createDoc('B', 'examples-1/y/index.html'),
|
||||
createDoc('C', 'examples-2/s/app.js'),
|
||||
createDoc('D', 'examples-2/t/style.css'),
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(exampleMap['examples-1']['x/app.js'].renderedContent).toEqual('PARSED:A');
|
||||
expect(exampleMap['examples-1']['y/index.html'].renderedContent).toEqual('PARSED:B');
|
||||
expect(exampleMap['examples-2']['s/app.js'].renderedContent).toEqual('PARSED:C');
|
||||
expect(exampleMap['examples-2']['t/style.css'].renderedContent).toEqual('PARSED:D');
|
||||
|
||||
});
|
||||
|
||||
it('should create region docs for each region in the example file docs', () => {
|
||||
const docs = [
|
||||
createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'),
|
||||
createDoc('<!-- #docregion Y -->\nB', 'examples-1/y/index.html'),
|
||||
createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'),
|
||||
];
|
||||
|
||||
const newDocs = processor.$process(docs);
|
||||
|
||||
expect(newDocs.length).toEqual(3);
|
||||
expect(newDocs).toEqual([
|
||||
jasmine.objectContaining({
|
||||
docType: 'example-region',
|
||||
name: 'dummy',
|
||||
id: 'examples-1/x/app.js#dummy',
|
||||
contents: 'js'
|
||||
}),
|
||||
jasmine.objectContaining({
|
||||
docType: 'example-region',
|
||||
name: 'dummy',
|
||||
id: 'examples-1/y/index.html#dummy',
|
||||
contents: 'html'
|
||||
}),
|
||||
jasmine.objectContaining({
|
||||
docType: 'example-region',
|
||||
name: 'dummy',
|
||||
id: 'examples-2/t/style.css#dummy',
|
||||
contents: 'css'
|
||||
})
|
||||
]);
|
||||
});
|
||||
|
||||
it('should attach region docs to the example file docs', () => {
|
||||
const docs = [
|
||||
createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'),
|
||||
createDoc('<!-- #docregion Y -->\nB', 'examples-1/y/index.html'),
|
||||
createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'),
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(exampleMap['examples-1']['x/app.js'].regions).toEqual({
|
||||
dummy: {
|
||||
docType: 'example-region',
|
||||
path: 'examples-1/x/app.js',
|
||||
name: 'dummy',
|
||||
id: 'examples-1/x/app.js#dummy',
|
||||
aliases: ['examples-1/x/app.js#dummy'],
|
||||
contents: 'js'
|
||||
}
|
||||
});
|
||||
expect(exampleMap['examples-1']['y/index.html'].regions).toEqual({
|
||||
dummy: {
|
||||
docType: 'example-region',
|
||||
path: 'examples-1/y/index.html',
|
||||
name: 'dummy',
|
||||
id: 'examples-1/y/index.html#dummy',
|
||||
aliases: ['examples-1/y/index.html#dummy'],
|
||||
contents: 'html'
|
||||
}
|
||||
});
|
||||
expect(exampleMap['examples-2']['t/style.css'].regions).toEqual({
|
||||
dummy: {
|
||||
docType: 'example-region',
|
||||
path: 'examples-2/t/style.css',
|
||||
name: 'dummy',
|
||||
id: 'examples-2/t/style.css#dummy',
|
||||
aliases: ['examples-2/t/style.css#dummy'],
|
||||
contents: 'css'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function createDoc(content, relativePath, docType) {
|
||||
return {
|
||||
fileInfo: {relativePath: relativePath, extension: path.extname(relativePath).substr(1)},
|
||||
content: content,
|
||||
docType: docType || 'example-file',
|
||||
startingLine: 1
|
||||
};
|
||||
}
|
3
tools/docs/examples-package/services/example-map.js
Normal file
3
tools/docs/examples-package/services/example-map.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function exampleMap() {
|
||||
return {};
|
||||
};
|
@ -0,0 +1,9 @@
|
||||
module.exports = function getExampleFilename() {
|
||||
|
||||
function getExampleFilenameImpl(relativePath) {
|
||||
return getExampleFilenameImpl.examplesFolder + relativePath;
|
||||
}
|
||||
|
||||
getExampleFilenameImpl.examplesFolder = '@angular/examples/';
|
||||
return getExampleFilenameImpl;
|
||||
};
|
55
tools/docs/examples-package/services/parseArgString.js
Normal file
55
tools/docs/examples-package/services/parseArgString.js
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @dgService parseArgString
|
||||
* @description
|
||||
* processes an arg string in 'almost' the same fashion that the command processor does
|
||||
* and returns an args object in yargs format.
|
||||
* @kind function
|
||||
* @param {String} str The arg string to process
|
||||
* @return {Object} The args parsed into a yargs format.
|
||||
*/
|
||||
|
||||
module.exports = function parseArgString() {
|
||||
|
||||
return function parseArgStringImpl(str) {
|
||||
// regex from npm string-argv
|
||||
//[^\s'"] Match if not a space ' or "
|
||||
|
||||
//+|['] or Match '
|
||||
//([^']*) Match anything that is not '
|
||||
//['] Close match if '
|
||||
|
||||
//+|["] or Match "
|
||||
//([^"]*) Match anything that is not "
|
||||
//["] Close match if "
|
||||
var rx = /[^\s'"]+|[']([^']*?)[']|["]([^"]*?)["]/gi;
|
||||
var value = str;
|
||||
var unnammedArgs = [];
|
||||
var args = {_: unnammedArgs};
|
||||
var match, key;
|
||||
do {
|
||||
// Each call to exec returns the next regex match as an array
|
||||
match = rx.exec(value);
|
||||
if (match !== null) {
|
||||
// Index 1 in the array is the captured group if it exists
|
||||
// Index 0 is the matched text, which we use if no captured group exists
|
||||
var arg = match[2] ? match[2] : (match[1] ? match[1] : match[0]);
|
||||
if (key) {
|
||||
args[key] = arg;
|
||||
key = null;
|
||||
} else {
|
||||
if (arg.substr(arg.length - 1) === '=') {
|
||||
key = arg.substr(0, arg.length - 1);
|
||||
// remove leading '-' if it exists.
|
||||
if (key.substr(0, 1) == '-') {
|
||||
key = key.substr(1);
|
||||
}
|
||||
} else {
|
||||
unnammedArgs.push(arg)
|
||||
key = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (match !== null);
|
||||
return args;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
// These kind of comments are used CSS and other languages that do not support inline comments
|
||||
module.exports = {
|
||||
regionStartMatcher: /^\s*\/\*\s*#docregion\s*(.*)\s*\*\/\s*$/,
|
||||
regionEndMatcher: /^\s*\/\*\s*#enddocregion\s*(.*)\s*\*\/\s*$/,
|
||||
plasterMatcher: /^\s*\/\*\s*#docplaster\s*(.*)\s*\*\/\s*$/,
|
||||
createPlasterComment: plaster => `/* ${plaster} */`
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
const matcher = require('./block-c');
|
||||
|
||||
describe('block-c region-matcher', () => {
|
||||
it('should match start annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('/* #docregion A b c */');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c ');
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('/*#docregion A b c*/');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('/* #docregion */');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should match end annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('/* #enddocregion A b c */');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c ');
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('/*#enddocregion A b c*/');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('/* #enddocregion */');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should match plaster annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.plasterMatcher.exec('/* #docplaster A b c */');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c ');
|
||||
|
||||
matches = matcher.plasterMatcher.exec('/*#docplaster A b c*/');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.plasterMatcher.exec('/* #docplaster */');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should create a plaster comment', () => {
|
||||
expect(matcher.createPlasterComment('... elided ...')).toEqual('/* ... elided ... */');
|
||||
});
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
// These kind of comments are used in HTML
|
||||
module.exports = {
|
||||
regionStartMatcher: /^\s*<!--\s*#docregion\s*(.*)\s*-->\s*$/,
|
||||
regionEndMatcher: /^\s*<!--\s*#enddocregion\s*(.*)\s*-->\s*$/,
|
||||
plasterMatcher: /^\s*<!--\s*#docplaster\s*(.*)\s*-->\s*$/,
|
||||
createPlasterComment: plaster => `<!-- ${plaster} -->`
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
const matcher = require('./html');
|
||||
|
||||
describe('html region-matcher', () => {
|
||||
it('should match start annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('<!-- #docregion A b c -->');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c ');
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('<!--#docregion A b c-->');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('<!-- #docregion -->');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should match end annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('<!-- #enddocregion A b c -->');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c ');
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('<!--#enddocregion A b c-->');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('<!-- #enddocregion -->');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should match plaster annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.plasterMatcher.exec('<!-- #docplaster A b c -->');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c ');
|
||||
|
||||
matches = matcher.plasterMatcher.exec('<!--#docplaster A b c-->');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.plasterMatcher.exec('<!-- #docplaster -->');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should create a plaster comment', () => {
|
||||
expect(matcher.createPlasterComment('... elided ...')).toEqual('<!-- ... elided ... -->');
|
||||
});
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
// These kind of comments are used in languages that do not support block comments, such as Jade
|
||||
module.exports = {
|
||||
regionStartMatcher: /^\s*\/\/\s*#docregion\s*(.*)\s*$/,
|
||||
regionEndMatcher: /^\s*\/\/\s*#enddocregion\s*(.*)\s*$/,
|
||||
plasterMatcher: /^\s*\/\/\s*#docplaster\s*(.*)\s*$/,
|
||||
createPlasterComment: plaster => `// ${plaster}`
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
const matcher = require('./inline-c-only');
|
||||
|
||||
describe('inline-c-only region-matcher', () => {
|
||||
it('should match start annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('// #docregion A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('//#docregion A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('// #docregion');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should match end annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('// #enddocregion A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('//#enddocregion A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('// #enddocregion');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should match plaster annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.plasterMatcher.exec('// #docplaster A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.plasterMatcher.exec('//#docplaster A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.plasterMatcher.exec('// #docplaster');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should create a plaster comment', () => {
|
||||
expect(matcher.createPlasterComment('... elided ...')).toEqual('// ... elided ...');
|
||||
});
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
// This comment type is used in C like languages such as JS, TS, Dart, etc
|
||||
module.exports = {
|
||||
regionStartMatcher: /^\s*\/\/\s*#docregion\s*(.*)\s*$/,
|
||||
regionEndMatcher: /^\s*\/\/\s*#enddocregion\s*(.*)\s*$/,
|
||||
plasterMatcher: /^\s*\/\/\s*#docplaster\s*(.*)\s*$/,
|
||||
createPlasterComment: plaster => `/* ${plaster} */`
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
const matcher = require('./inline-c');
|
||||
|
||||
describe('inline-c region-matcher', () => {
|
||||
it('should match start annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('// #docregion A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('//#docregion A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('// #docregion');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should match end annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('// #enddocregion A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('//#enddocregion A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('// #enddocregion');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should match plaster annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.plasterMatcher.exec('// #docplaster A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.plasterMatcher.exec('//#docplaster A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.plasterMatcher.exec('// #docplaster');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should create a plaster comment', () => {
|
||||
expect(matcher.createPlasterComment('... elided ...')).toEqual('/* ... elided ... */');
|
||||
});
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
// These type of comments are used in hash comment based languages such as bash and Yaml
|
||||
module.exports = {
|
||||
regionStartMatcher: /^\s*#\s*#docregion\s*(.*)\s*$/,
|
||||
regionEndMatcher: /^\s*#\s*#enddocregion\s*(.*)\s*$/,
|
||||
plasterMatcher: /^\s*#\s*#docplaster\s*(.*)\s*$/,
|
||||
createPlasterComment: plaster => `# ${plaster}`
|
||||
};
|
@ -0,0 +1,54 @@
|
||||
const matcher = require('./inline-hash');
|
||||
|
||||
describe('inline-hash region-matcher', () => {
|
||||
it('should match start annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('# #docregion A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('##docregion A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionStartMatcher.exec('# #docregion');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should match end annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('# #enddocregion A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('##enddocregion A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.regionEndMatcher.exec('# #enddocregion');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should match plaster annotations', () => {
|
||||
let matches;
|
||||
|
||||
matches = matcher.plasterMatcher.exec('# #docplaster A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.plasterMatcher.exec('##docplaster A b c');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('A b c');
|
||||
|
||||
matches = matcher.plasterMatcher.exec('# #docplaster');
|
||||
expect(matches).not.toBeNull();
|
||||
expect(matches[1]).toEqual('');
|
||||
});
|
||||
|
||||
it('should create a plaster comment',
|
||||
() => { expect(matcher.createPlasterComment('... elided ...')).toEqual('# ... elided ...'); });
|
||||
});
|
112
tools/docs/examples-package/services/region-parser.js
Normal file
112
tools/docs/examples-package/services/region-parser.js
Normal file
@ -0,0 +1,112 @@
|
||||
const blockC = require('./region-matchers/block-c');
|
||||
const html = require('./region-matchers/html');
|
||||
const inlineC = require('./region-matchers/inline-c');
|
||||
const inlineCOnly = require('./region-matchers/inline-c-only');
|
||||
const inlineHash = require('./region-matchers/inline-hash');
|
||||
const NO_NAME_REGION = '';
|
||||
const DEFAULT_PLASTER = '. . .';
|
||||
const {mapObject} = require('../utils');
|
||||
|
||||
module.exports = function regionParser() {
|
||||
return regionParserImpl;
|
||||
};
|
||||
|
||||
regionParserImpl.regionMatchers = {
|
||||
ts: inlineC,
|
||||
js: inlineC,
|
||||
es6: inlineC,
|
||||
dart: inlineC,
|
||||
html: html,
|
||||
css: blockC,
|
||||
yaml: inlineHash,
|
||||
jade: inlineCOnly
|
||||
};
|
||||
|
||||
/**
|
||||
* @param contents string
|
||||
* @param fileType string
|
||||
* @returns {contents: string, regions: {[regionName: string]: string}}
|
||||
*/
|
||||
function regionParserImpl(contents, fileType) {
|
||||
const regionMatcher = regionParserImpl.regionMatchers[fileType];
|
||||
const openRegions = [];
|
||||
const regionMap = {};
|
||||
|
||||
if (regionMatcher) {
|
||||
let plaster = regionMatcher.createPlasterComment(DEFAULT_PLASTER);
|
||||
const lines = contents.split(/\r?\n/).filter((line, index) => {
|
||||
const startRegion = line.match(regionMatcher.regionStartMatcher);
|
||||
const endRegion = line.match(regionMatcher.regionEndMatcher);
|
||||
const updatePlaster = line.match(regionMatcher.plasterMatcher);
|
||||
|
||||
// start region processing
|
||||
if (startRegion) {
|
||||
// open up the specified region
|
||||
const regionName = getRegionName(startRegion[1]);
|
||||
const region = regionMap[regionName];
|
||||
if (region) {
|
||||
if (region.open) {
|
||||
throw new RegionParserError(
|
||||
`Tried to open a region, named "${regionName}", that is already open`, index);
|
||||
}
|
||||
region.open = true;
|
||||
region.lines.push(plaster);
|
||||
} else {
|
||||
regionMap[regionName] = {lines: [], open: true};
|
||||
}
|
||||
openRegions.push(regionName);
|
||||
|
||||
// end region processing
|
||||
} else if (endRegion) {
|
||||
if (openRegions.length === 0) {
|
||||
throw new RegionParserError('Tried to close a region when none are open', index);
|
||||
}
|
||||
// close down the specified region (or most recent if no name is given)
|
||||
const regionName = getRegionName(endRegion[1]) || openRegions[openRegions.length - 1];
|
||||
const region = regionMap[regionName];
|
||||
if (!region || !region.open) {
|
||||
throw new RegionParserError(
|
||||
`Tried to close a region, named "${regionName}", that is not open`, index);
|
||||
}
|
||||
region.open = false;
|
||||
removeLast(openRegions, regionName);
|
||||
|
||||
// doc plaster processing
|
||||
} else if (updatePlaster) {
|
||||
plaster = regionMatcher.createPlasterComment(updatePlaster[1].trim());
|
||||
|
||||
// simple line of content processing
|
||||
} else {
|
||||
openRegions.forEach(regionName => regionMap[regionName].lines.push(line));
|
||||
// do not filter out this line from the content
|
||||
return true;
|
||||
}
|
||||
|
||||
// this line contained an annotation so let's filter it out
|
||||
return false;
|
||||
});
|
||||
return {
|
||||
contents: lines.join('\n'),
|
||||
regions: mapObject(regionMap, (regionName, region) => region.lines.join('\n'))
|
||||
};
|
||||
} else {
|
||||
return {contents, regions: {}};
|
||||
}
|
||||
}
|
||||
|
||||
function getRegionName(input) {
|
||||
return input.trim();
|
||||
}
|
||||
|
||||
function removeLast(array, item) {
|
||||
const index = array.lastIndexOf(item);
|
||||
array.splice(index, 1);
|
||||
}
|
||||
|
||||
function RegionParserError(message, lineNum) {
|
||||
this.message = `regionParser: ${message} (at line ${lineNum}).`;
|
||||
this.lineNum = lineNum;
|
||||
this.stack = (new Error()).stack;
|
||||
}
|
||||
RegionParserError.prototype = Object.create(Error.prototype);
|
||||
RegionParserError.prototype.constructor = RegionParserError;
|
143
tools/docs/examples-package/services/region-parser.spec.js
Normal file
143
tools/docs/examples-package/services/region-parser.spec.js
Normal file
@ -0,0 +1,143 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
const testRegionMatcher = {
|
||||
regionStartMatcher: /^\s*\/\*\s*#docregion\s+(.*)\s*\*\/\s*$/,
|
||||
regionEndMatcher: /^\s*\/\*\s*#enddocregion\s+(.*)\s*\*\/\s*$/,
|
||||
plasterMatcher: /^\s*\/\*\s*#docplaster\s+(.*)\s*\*\/\s*$/,
|
||||
createPlasterComment: plaster => `/* ${plaster} */`
|
||||
};
|
||||
|
||||
describe('regionParser service', () => {
|
||||
var dgeni, injector, regionParser;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('examples-package', true)]);
|
||||
injector = dgeni.configureInjector();
|
||||
regionParser = injector.get('regionParser');
|
||||
regionParser.regionMatchers = {'test-type': testRegionMatcher};
|
||||
});
|
||||
|
||||
it('should return just the contents if there is no region-matcher for the file type', () => {
|
||||
const output = regionParser('some contents', 'unknown');
|
||||
expect(output).toEqual({contents: 'some contents', regions: {}});
|
||||
});
|
||||
|
||||
it('should return just the contents if there is a region-matcher but no regions', () => {
|
||||
const output = regionParser('some contents', 'test-type');
|
||||
expect(output).toEqual({contents: 'some contents', regions: {}});
|
||||
});
|
||||
|
||||
it('should remove start region annotations from the contents', () => {
|
||||
const output = regionParser(
|
||||
t('/* #docregion */', 'abc', '/* #docregion X */', 'def', '/* #docregion Y */', 'ghi'),
|
||||
'test-type');
|
||||
expect(output.contents).toEqual(t('abc', 'def', 'ghi'));
|
||||
});
|
||||
|
||||
it('should remove end region annotations from the contents', () => {
|
||||
const output = regionParser(
|
||||
t('/* #docregion */', 'abc', '/* #docregion X */', 'def', '/* #enddocregion X */',
|
||||
'/* #docregion Y */', 'ghi', '/* #enddocregion Y */', '/* #enddocregion */'),
|
||||
'test-type');
|
||||
expect(output.contents).toEqual(t('abc', 'def', 'ghi'));
|
||||
});
|
||||
|
||||
|
||||
it('should remove doc plaster annotations from the contents', () => {
|
||||
const output =
|
||||
regionParser(t('/* #docplaster ... elided ... */', 'abc', 'def', 'ghi'), 'test-type');
|
||||
expect(output.contents).toEqual(t('abc', 'def', 'ghi'));
|
||||
});
|
||||
|
||||
it('should capture the rest of the contents for a region with no end region annotation', () => {
|
||||
const output = regionParser(
|
||||
t('/* #docregion */', 'abc', '/* #docregion X */', 'def', '/* #docregion Y */', 'ghi'),
|
||||
'test-type');
|
||||
expect(output.regions['']).toEqual(t('abc', 'def', 'ghi'));
|
||||
expect(output.regions['X']).toEqual(t('def', 'ghi'));
|
||||
expect(output.regions['Y']).toEqual(t('ghi'));
|
||||
});
|
||||
|
||||
|
||||
it('should capture the contents for a region up to the end region annotation', () => {
|
||||
const output = regionParser(
|
||||
t('/* #docregion */', 'abc', '/* #enddocregion */', '/* #docregion X */', 'def',
|
||||
'/* #enddocregion X */', '/* #docregion Y */', 'ghi', '/* #enddocregion Y */'),
|
||||
'test-type');
|
||||
expect(output.regions['']).toEqual(t('abc'));
|
||||
expect(output.regions['X']).toEqual(t('def'));
|
||||
expect(output.regions['Y']).toEqual(t('ghi'));
|
||||
});
|
||||
|
||||
it('should close the most recently opened region if there is no region name', () => {
|
||||
const output = regionParser(
|
||||
t('/* #docregion X*/', 'abc', '/* #docregion Y */', 'def', '/* #enddocregion */', 'ghi',
|
||||
'/* #enddocregion */'),
|
||||
'test-type');
|
||||
expect(output.regions['X']).toEqual(t('abc', 'def', 'ghi'));
|
||||
expect(output.regions['Y']).toEqual(t('def'));
|
||||
});
|
||||
|
||||
it('should handle overlapping regions', () => {
|
||||
const output = regionParser(
|
||||
t('/* #docregion X*/', 'abc', '/* #docregion Y */', 'def', '/* #enddocregion X */', 'ghi',
|
||||
'/* #enddocregion Y */'),
|
||||
'test-type');
|
||||
expect(output.regions['X']).toEqual(t('abc', 'def'));
|
||||
expect(output.regions['Y']).toEqual(t('def', 'ghi'));
|
||||
});
|
||||
|
||||
it('should error if we attempt to open an already open region', () => {
|
||||
expect(() => regionParser(t('/* #docregion */', 'abc', '/* #docregion */', 'def'), 'test-type'))
|
||||
.toThrowError(
|
||||
'regionParser: Tried to open a region, named "", that is already open (at line 2).');
|
||||
|
||||
expect(
|
||||
() =>
|
||||
regionParser(t('/* #docregion X */', 'abc', '/* #docregion X */', 'def'), 'test-type'))
|
||||
.toThrowError(
|
||||
'regionParser: Tried to open a region, named "X", that is already open (at line 2).');
|
||||
});
|
||||
|
||||
it('should error if we attempt to close an already closed region', () => {
|
||||
expect(() => regionParser(t('abc', '/* #enddocregion */', 'def'), 'test-type'))
|
||||
.toThrowError('regionParser: Tried to close a region when none are open (at line 1).');
|
||||
|
||||
expect(
|
||||
() =>
|
||||
regionParser(t('/* #docregion */', 'abc', '/* #enddocregion X */', 'def'), 'test-type'))
|
||||
.toThrowError(
|
||||
'regionParser: Tried to close a region, named "X", that is not open (at line 2).');
|
||||
});
|
||||
|
||||
it('should handle whitespace in region names on single annotation', () => {
|
||||
const output =
|
||||
regionParser(t('/* #docregion A B*/', 'abc', '/* #docregion A C */', 'def'), 'test-type');
|
||||
expect(output.regions['A B']).toEqual(t('abc', 'def'));
|
||||
expect(output.regions['A C']).toEqual(t('def'));
|
||||
});
|
||||
|
||||
it('should join multiple regions with the default plaster string (". . .")', () => {
|
||||
const output = regionParser(
|
||||
t('/* #docregion */', 'abc', '/* #enddocregion */', 'def', '/* #docregion */', 'ghi',
|
||||
'/* #enddocregion */'),
|
||||
'test-type');
|
||||
expect(output.regions['']).toEqual(t('abc', '/* . . . */', 'ghi'));
|
||||
});
|
||||
|
||||
|
||||
it('should join multiple regions with the current plaster string', () => {
|
||||
const output = regionParser(
|
||||
t('/* #docregion */', 'abc', '/* #enddocregion */', 'def', '/* #docregion */', 'ghi',
|
||||
'/* #enddocregion */', '/* #docplaster ... elided ... */', '/* #docregion A */', 'jkl',
|
||||
'/* #enddocregion A */', 'mno', '/* #docregion A */', 'pqr', '/* #enddocregion A */'),
|
||||
'test-type');
|
||||
expect(output.regions['']).toEqual(t('abc', '/* . . . */', 'ghi'));
|
||||
expect(output.regions['A']).toEqual(t('jkl', '/* ... elided ... */', 'pqr'));
|
||||
});
|
||||
});
|
||||
|
||||
function t() {
|
||||
return Array.prototype.join.call(arguments, '\n');
|
||||
}
|
7
tools/docs/examples-package/utils.js
Normal file
7
tools/docs/examples-package/utils.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
mapObject(obj, mapper) {
|
||||
const mappedObj = {};
|
||||
Object.keys(obj).forEach(key => { mappedObj[key] = mapper(key, obj[key]); });
|
||||
return mappedObj;
|
||||
}
|
||||
};
|
23
tools/docs/helpers/test-package.js
Normal file
23
tools/docs/helpers/test-package.js
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
const Package = require('dgeni').Package;
|
||||
|
||||
module.exports = function testPackage(packageName, mockTemplateEngine) {
|
||||
|
||||
const pkg = new Package('mock_' + packageName, [require('../' + packageName)]);
|
||||
|
||||
// provide a mock log service
|
||||
pkg.factory('log', function() { return require('dgeni/lib/mocks/log')(false); });
|
||||
|
||||
if (mockTemplateEngine) {
|
||||
pkg.factory('templateEngine', function() { return {}; });
|
||||
}
|
||||
|
||||
return pkg;
|
||||
};
|
22
tools/docs/links-package/index.js
Normal file
22
tools/docs/links-package/index.js
Normal file
@ -0,0 +1,22 @@
|
||||
var Package = require('dgeni').Package;
|
||||
var jsdocPackage = require('dgeni-packages/jsdoc');
|
||||
|
||||
module.exports =
|
||||
new Package('links', [jsdocPackage])
|
||||
|
||||
.factory(require('./inline-tag-defs/link'))
|
||||
.factory(require('./services/getAliases'))
|
||||
.factory(require('./services/getDocFromAlias'))
|
||||
.factory(require('./services/getLinkInfo'))
|
||||
.factory(require('./services/moduleScopeLinkDisambiguator'))
|
||||
.factory(require('./services/deprecatedDocsLinkDisambiguator'))
|
||||
|
||||
.config(function(inlineTagProcessor, linkInlineTagDef) {
|
||||
inlineTagProcessor.inlineTagDefinitions.push(linkInlineTagDef);
|
||||
})
|
||||
|
||||
.config(function(
|
||||
getLinkInfo, moduleScopeLinkDisambiguator, deprecatedDocsLinkDisambiguator) {
|
||||
getLinkInfo.disambiguators.push(moduleScopeLinkDisambiguator);
|
||||
getLinkInfo.disambiguators.push(deprecatedDocsLinkDisambiguator);
|
||||
});
|
35
tools/docs/links-package/inline-tag-defs/link.js
Normal file
35
tools/docs/links-package/inline-tag-defs/link.js
Normal file
@ -0,0 +1,35 @@
|
||||
var INLINE_LINK = /(\S+)(?:\s+([\s\S]+))?/;
|
||||
|
||||
/**
|
||||
* @dgService linkInlineTagDef
|
||||
* @description
|
||||
* Process inline link tags (of the form {@link some/uri Some Title}), replacing them with HTML anchors
|
||||
* @kind function
|
||||
* @param {Object} url The url to match
|
||||
* @param {Function} docs error message
|
||||
* @return {String} The html link information
|
||||
*
|
||||
* @property {boolean} relativeLinks Whether we expect the links to be relative to the originating doc
|
||||
*/
|
||||
module.exports = function linkInlineTagDef(getLinkInfo, createDocMessage, log) {
|
||||
return {
|
||||
name: 'link',
|
||||
aliases: ['linkDocs'],
|
||||
description:
|
||||
'Process inline link tags (of the form {@link some/uri Some Title}), replacing them with HTML anchors',
|
||||
handler: function(doc, tagName, tagDescription) {
|
||||
|
||||
// Parse out the uri and title
|
||||
return tagDescription.replace(INLINE_LINK, function(match, uri, title) {
|
||||
|
||||
var linkInfo = getLinkInfo(uri, title, doc);
|
||||
|
||||
if (!linkInfo.valid) {
|
||||
log.warn(createDocMessage(linkInfo.error, doc));
|
||||
}
|
||||
|
||||
return '<a href=\'' + linkInfo.url + '\'>' + linkInfo.title + '</a>';
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
@ -0,0 +1,12 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function deprecatedDocsLinkDisambiguator() {
|
||||
return function(url, title, currentDoc, docs) {
|
||||
if (docs.length != 2) return docs;
|
||||
|
||||
var filteredDocs = _.filter(
|
||||
docs, function(doc) { return !doc.fileInfo.relativePath.match(/\/(\w+)-deprecated\//); });
|
||||
|
||||
return filteredDocs.length > 0 ? filteredDocs : docs;
|
||||
};
|
||||
};
|
71
tools/docs/links-package/services/getAliases.js
Normal file
71
tools/docs/links-package/services/getAliases.js
Normal file
@ -0,0 +1,71 @@
|
||||
|
||||
function parseCodeName(codeName) {
|
||||
var parts = [];
|
||||
var currentPart;
|
||||
|
||||
codeName.split('.').forEach(function(part) {
|
||||
var subParts = part.split(':');
|
||||
|
||||
var name = subParts.pop();
|
||||
var modifier = subParts.pop();
|
||||
|
||||
if (!modifier && currentPart) {
|
||||
currentPart.name += '.' + name;
|
||||
} else {
|
||||
currentPart = {name: name, modifier: modifier};
|
||||
parts.push(currentPart);
|
||||
}
|
||||
});
|
||||
return parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dgService getAliases
|
||||
* @description
|
||||
* Get a list of all the aliases that can be made from the doc
|
||||
* @param {Object} doc A doc from which to extract aliases
|
||||
* @return {Array} A collection of aliases
|
||||
*/
|
||||
module.exports = function getAliases() {
|
||||
|
||||
return function(doc) {
|
||||
|
||||
var codeNameParts = parseCodeName(doc.id);
|
||||
|
||||
var methodName;
|
||||
var aliases = [];
|
||||
// Add the last part to the list of aliases
|
||||
var part = codeNameParts.pop();
|
||||
|
||||
// If the name contains a # then it is a member and that should be included in the aliases
|
||||
if (part.name.indexOf('#') !== -1) {
|
||||
methodName = part.name.split('#')[1];
|
||||
}
|
||||
// Add the part name and modifier, if provided
|
||||
aliases.push(part.name);
|
||||
if (part.modifier) {
|
||||
aliases.push(part.modifier + ':' + part.name);
|
||||
}
|
||||
|
||||
// Continue popping off the parts of the codeName and work forward collecting up each alias
|
||||
aliases = codeNameParts.reduceRight(function(aliases, part) {
|
||||
|
||||
// Add this part to each of the aliases we have so far
|
||||
aliases.forEach(function(name) {
|
||||
// Add the part name and modifier, if provided
|
||||
aliases.push(part.name + '.' + name);
|
||||
if (part.modifier) {
|
||||
aliases.push(part.modifier + ':' + part.name + '.' + name);
|
||||
}
|
||||
});
|
||||
|
||||
return aliases;
|
||||
}, aliases);
|
||||
|
||||
if (methodName) {
|
||||
aliases.push(methodName);
|
||||
}
|
||||
|
||||
return aliases;
|
||||
};
|
||||
};
|
14
tools/docs/links-package/services/getAliases.spec.js
Normal file
14
tools/docs/links-package/services/getAliases.spec.js
Normal file
@ -0,0 +1,14 @@
|
||||
var getAliasesFactory = require('./getAliases');
|
||||
|
||||
describe('getAliases', function() {
|
||||
|
||||
it('should extract all the parts from a code name', function() {
|
||||
|
||||
var getAliases = getAliasesFactory();
|
||||
|
||||
expect(getAliases({id: 'module:ng.service:$http#get'})).toEqual([
|
||||
'$http#get', 'service:$http#get', 'ng.$http#get', 'module:ng.$http#get',
|
||||
'ng.service:$http#get', 'module:ng.service:$http#get', 'get'
|
||||
]);
|
||||
});
|
||||
});
|
31
tools/docs/links-package/services/getDocFromAlias.js
Normal file
31
tools/docs/links-package/services/getDocFromAlias.js
Normal file
@ -0,0 +1,31 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* @dgService getDocFromAlias
|
||||
* @description Get an array of docs that match this alias, relative to the originating doc.
|
||||
*/
|
||||
module.exports = function getDocFromAlias(aliasMap, log) {
|
||||
|
||||
return function getDocFromAlias(alias, originatingDoc) {
|
||||
var docs = aliasMap.getDocs(alias);
|
||||
|
||||
// If there is more than one item with this name then try to filter them by the originatingDoc's
|
||||
// area
|
||||
if (docs.length > 1 && originatingDoc && originatingDoc.area) {
|
||||
docs = _.filter(docs, function(doc) { return doc.area === originatingDoc.area; });
|
||||
}
|
||||
|
||||
// If filtering by area left us with none then let's start again
|
||||
if (docs.length === 0) {
|
||||
docs = aliasMap.getDocs(alias);
|
||||
}
|
||||
|
||||
// If there is more than one item with this name then try to filter them by the originatingDoc's
|
||||
// module
|
||||
if (docs.length > 1 && originatingDoc && originatingDoc.module) {
|
||||
docs = _.filter(docs, function(doc) { return doc.module === originatingDoc.module; });
|
||||
}
|
||||
|
||||
return docs;
|
||||
};
|
||||
};
|
48
tools/docs/links-package/services/getDocFromAlias.spec.js
Normal file
48
tools/docs/links-package/services/getDocFromAlias.spec.js
Normal file
@ -0,0 +1,48 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
var getDocFromAlias, aliasMap;
|
||||
|
||||
describe('getDocFromAlias', function() {
|
||||
beforeEach(function() {
|
||||
var dgeni = new Dgeni([testPackage('links-package', true)]);
|
||||
var injector = dgeni.configureInjector();
|
||||
aliasMap = injector.get('aliasMap');
|
||||
getDocFromAlias = injector.get('getDocFromAlias');
|
||||
});
|
||||
|
||||
it('should return an array of docs that match the alias', function() {
|
||||
var doc1 = {aliases: ['a', 'b', 'c']};
|
||||
var doc2 = {aliases: ['a', 'b']};
|
||||
var doc3 = {aliases: ['a']};
|
||||
aliasMap.addDoc(doc1);
|
||||
aliasMap.addDoc(doc2);
|
||||
aliasMap.addDoc(doc3);
|
||||
|
||||
expect(getDocFromAlias('a')).toEqual([doc1, doc2, doc3]);
|
||||
expect(getDocFromAlias('b')).toEqual([doc1, doc2]);
|
||||
expect(getDocFromAlias('c')).toEqual([doc1]);
|
||||
});
|
||||
|
||||
it('should return docs that match the alias and originating doc\'s area', function() {
|
||||
var doc1 = {aliases: ['a'], area: 'api'};
|
||||
var doc2 = {aliases: ['a'], area: 'api'};
|
||||
var doc3 = {aliases: ['a'], area: 'other'};
|
||||
aliasMap.addDoc(doc1);
|
||||
aliasMap.addDoc(doc2);
|
||||
aliasMap.addDoc(doc3);
|
||||
|
||||
expect(getDocFromAlias('a', {area: 'api'})).toEqual([doc1, doc2]);
|
||||
});
|
||||
|
||||
it('should return docs that match the alias and originating doc\'s area and module', function() {
|
||||
var doc1 = {aliases: ['a'], area: 'api', module: 'ng'};
|
||||
var doc2 = {aliases: ['a'], area: 'api', module: 'ngMock'};
|
||||
var doc3 = {aliases: ['a'], area: 'other', module: 'ng'};
|
||||
aliasMap.addDoc(doc1);
|
||||
aliasMap.addDoc(doc2);
|
||||
aliasMap.addDoc(doc3);
|
||||
|
||||
expect(getDocFromAlias('a', {area: 'api', module: 'ng'})).toEqual([doc1]);
|
||||
});
|
||||
});
|
78
tools/docs/links-package/services/getLinkInfo.js
Normal file
78
tools/docs/links-package/services/getLinkInfo.js
Normal file
@ -0,0 +1,78 @@
|
||||
var path = require('canonical-path');
|
||||
|
||||
/**
|
||||
* @dgService getLinkInfo
|
||||
* @description
|
||||
* Get link information to a document that matches the given url
|
||||
* @kind function
|
||||
* @param {String} url The url to match
|
||||
* @param {String} title An optional title to return in the link information
|
||||
* @return {Object} The link information
|
||||
*
|
||||
* @property {boolean} relativeLinks Whether we expect the links to be relative to the originating doc
|
||||
* @property {array<function(url, title, currentDoc, ambiguousDocs) : array} disambiguators a collection of functions
|
||||
* that attempt to resolve ambiguous links. Each disambiguator returns a new collection of docs with
|
||||
* unwanted ambiguous docs removed (see moduleScopeLinkDisambiguator service for an example).
|
||||
*/
|
||||
module.exports = function getLinkInfo(getDocFromAlias, encodeCodeBlock, log) {
|
||||
|
||||
getLinkInfoImpl.disambiguators = [];
|
||||
|
||||
return getLinkInfoImpl;
|
||||
|
||||
function getLinkInfoImpl(url, title, currentDoc) {
|
||||
var linkInfo = {url: url, type: 'url', valid: true, title: title || url};
|
||||
|
||||
if (!url) {
|
||||
throw new Error('Invalid url');
|
||||
}
|
||||
|
||||
var docs = getDocFromAlias(url, currentDoc);
|
||||
|
||||
// Give each disambiguator a chance to reduce the number of ambiguous docs
|
||||
docs = getLinkInfoImpl.disambiguators.reduce(function(docs, disambiguator) {
|
||||
return disambiguator(url, title, currentDoc, docs);
|
||||
}, docs);
|
||||
|
||||
if (!getLinkInfoImpl.useFirstAmbiguousLink && docs.length > 1) {
|
||||
linkInfo.valid = false;
|
||||
linkInfo.errorType = 'ambiguous';
|
||||
linkInfo.error = 'Ambiguous link: "' + url + '".\n' + docs.reduce(function(msg, doc) {
|
||||
return msg + '\n "' + doc.id + '" (' + doc.docType + ') : (' + doc.path + ' / ' +
|
||||
doc.fileInfo.relativePath + ')';
|
||||
}, 'Matching docs: ');
|
||||
|
||||
} else if (docs.length >= 1) {
|
||||
linkInfo.url = docs[0].path;
|
||||
linkInfo.title = title || encodeCodeBlock(docs[0].name, true);
|
||||
linkInfo.type = 'doc';
|
||||
|
||||
if (getLinkInfoImpl.relativeLinks && currentDoc && currentDoc.path) {
|
||||
var currentFolder = path.dirname(currentDoc.path);
|
||||
var docFolder = path.dirname(linkInfo.url);
|
||||
var relativeFolder =
|
||||
path.relative(path.join('/', currentFolder), path.join('/', docFolder));
|
||||
linkInfo.url = path.join(relativeFolder, path.basename(linkInfo.url));
|
||||
log.debug(currentDoc.path, docs[0].path, linkInfo.url);
|
||||
}
|
||||
|
||||
} else if (url.indexOf('#') > 0) {
|
||||
var pathAndHash = url.split('#');
|
||||
linkInfo = getLinkInfoImpl(pathAndHash[0], title, currentDoc);
|
||||
linkInfo.url = linkInfo.url + '#' + pathAndHash[1];
|
||||
return linkInfo;
|
||||
|
||||
} else if (url.indexOf('/') === -1 && url.indexOf('#') !== 0) {
|
||||
linkInfo.valid = false;
|
||||
linkInfo.errorType = 'missing';
|
||||
linkInfo.error = 'Invalid link (does not match any doc): "' + url + '"';
|
||||
|
||||
} else {
|
||||
linkInfo.title =
|
||||
title || ((url.indexOf('#') === 0) ? url.substring(1) : path.basename(url, '.html'));
|
||||
}
|
||||
|
||||
return linkInfo;
|
||||
};
|
||||
|
||||
};
|
@ -0,0 +1,15 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function moduleScopeLinkDisambiguator() {
|
||||
return function(url, title, currentDoc, docs) {
|
||||
if (docs.length > 1) {
|
||||
// filter out target docs that are not in the same module as the source doc
|
||||
var filteredDocs =
|
||||
_.filter(docs, function(doc) { return doc.moduleDoc === currentDoc.moduleDoc; });
|
||||
// if all target docs are in a different module then just return the full collection of
|
||||
// ambiguous docs
|
||||
return filteredDocs.length > 0 ? filteredDocs : docs;
|
||||
}
|
||||
return docs;
|
||||
};
|
||||
};
|
10
tools/docs/target-package/index.js
Normal file
10
tools/docs/target-package/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
var Package = require('dgeni').Package;
|
||||
|
||||
module.exports = new Package('target', [require('dgeni-packages/jsdoc')])
|
||||
|
||||
.factory(require('./services/targetEnvironments'))
|
||||
.factory(require('./inline-tag-defs/target'))
|
||||
|
||||
.config(function(inlineTagProcessor, targetInlineTagDef) {
|
||||
inlineTagProcessor.inlineTagDefinitions.push(targetInlineTagDef);
|
||||
});
|
33
tools/docs/target-package/inline-tag-defs/target.js
Normal file
33
tools/docs/target-package/inline-tag-defs/target.js
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @dgService
|
||||
* @description
|
||||
* Process inline `target` block tags
|
||||
* (of the form `{@target environment1 environment2}...{@endtarget}`),
|
||||
* filtering out the blocks that do not match the active `targetEnvironments`.
|
||||
*/
|
||||
module.exports = function targetInlineTagDef(targetEnvironments, log, createDocMessage) {
|
||||
return {
|
||||
name: 'target',
|
||||
end: 'endtarget',
|
||||
handler: function(doc, tagName, tagDescription) {
|
||||
var targets = tagDescription && tagDescription.tag.split(' ');
|
||||
var hasTargets = targets && targets.length;
|
||||
|
||||
try {
|
||||
// Return the contents of this block if any of the following is true:
|
||||
// * it has no targets
|
||||
// * there are no targets stored in the targetEnvironments service
|
||||
// * the block's targets overlap with the active targets in the targetEnvironments service
|
||||
if (!hasTargets || !targetEnvironments.hasActive() ||
|
||||
targetEnvironments.someActive(targets)) {
|
||||
return tagDescription.content;
|
||||
}
|
||||
} catch (x) {
|
||||
log.error(createDocMessage('Error processing target inline tag def - ' + x.message, doc));
|
||||
}
|
||||
|
||||
// Otherwise return an empty string
|
||||
return '';
|
||||
}
|
||||
};
|
||||
};
|
40
tools/docs/target-package/inline-tag-defs/target.spec.js
Normal file
40
tools/docs/target-package/inline-tag-defs/target.spec.js
Normal file
@ -0,0 +1,40 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('target inline-tag-def', function() {
|
||||
var dgeni, injector, targetInlineTagDef;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('target-package', true)]);
|
||||
injector = dgeni.configureInjector();
|
||||
targetInlineTagDef = injector.get('targetInlineTagDef');
|
||||
});
|
||||
|
||||
|
||||
it('should filter out content that does not match the targetEnvironments', function() {
|
||||
|
||||
var doc = {};
|
||||
|
||||
var targetEnvironments = injector.get('targetEnvironments');
|
||||
targetEnvironments.addAllowed('js', true);
|
||||
targetEnvironments.addAllowed('es6', true);
|
||||
targetEnvironments.addAllowed('ts', false);
|
||||
|
||||
var result = targetInlineTagDef.handler(doc, 'target', {tag: 'es6 ts', content: 'abc'});
|
||||
expect(result).toEqual('abc');
|
||||
|
||||
result = targetInlineTagDef.handler(doc, 'target', {tag: 'ts', content: 'xyz'});
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
|
||||
|
||||
it('should not filter anything if there are no doc nor global target environments', function() {
|
||||
var doc = {};
|
||||
|
||||
var result = targetInlineTagDef.handler(doc, 'target', {tag: 'es6 ts', content: 'abc'});
|
||||
expect(result).toEqual('abc');
|
||||
|
||||
result = targetInlineTagDef.handler(doc, 'target', {tag: 'ts', content: 'xyz'});
|
||||
expect(result).toEqual('xyz');
|
||||
});
|
||||
});
|
51
tools/docs/target-package/services/targetEnvironments.js
Normal file
51
tools/docs/target-package/services/targetEnvironments.js
Normal file
@ -0,0 +1,51 @@
|
||||
module.exports = function targetEnvironments() {
|
||||
var _targets = Object.create(null);
|
||||
var _activeCount = 0;
|
||||
|
||||
var checkAllowed = function(target) {
|
||||
if (!(target in _targets)) {
|
||||
throw new Error(
|
||||
'Error accessing target "' + target + '". It is not in the list of allowed targets: ' +
|
||||
Object.keys(_targets));
|
||||
}
|
||||
};
|
||||
|
||||
var updateActiveCount = function() {
|
||||
_activeCount = 0;
|
||||
for (target in _targets) {
|
||||
if (_targets[target]) _activeCount++;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
addAllowed: function(target, isActive) {
|
||||
_targets[target] = !!isActive;
|
||||
updateActiveCount();
|
||||
},
|
||||
removeAllowed: function(target) {
|
||||
delete _targets[target];
|
||||
updateActiveCount();
|
||||
},
|
||||
activate: function(target) {
|
||||
checkAllowed(target);
|
||||
_targets[target] = true;
|
||||
updateActiveCount();
|
||||
},
|
||||
deactivate: function(target) {
|
||||
checkAllowed(target);
|
||||
_targets[target] = false;
|
||||
updateActiveCount();
|
||||
},
|
||||
isActive: function(target) {
|
||||
checkAllowed(target);
|
||||
return _targets[target];
|
||||
},
|
||||
hasActive: function() { return _activeCount > 0; },
|
||||
someActive: function(targets) {
|
||||
for (var i = 0, ii = targets.length; i < ii; i++) {
|
||||
if (this.isActive(targets[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
};
|
108
tools/docs/target-package/services/targetEnvironments.spec.js
Normal file
108
tools/docs/target-package/services/targetEnvironments.spec.js
Normal file
@ -0,0 +1,108 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('target inline-tag-def', function() {
|
||||
var dgeni, injector, te;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('target-package', true)]);
|
||||
injector = dgeni.configureInjector();
|
||||
te = injector.get('targetEnvironments');
|
||||
});
|
||||
|
||||
describe('addAllowed', function() {
|
||||
it('should store the target and whether it is active', function() {
|
||||
te.addAllowed('a', true);
|
||||
te.addAllowed('b', false);
|
||||
te.addAllowed('c');
|
||||
expect(te.isActive('a')).toBe(true);
|
||||
expect(te.isActive('b')).toBe(false);
|
||||
expect(te.isActive('c')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeAllowed', function() {
|
||||
it('should disallow the target', function() {
|
||||
te.addAllowed('a');
|
||||
te.addAllowed('b');
|
||||
te.removeAllowed('b');
|
||||
expect(te.isActive('a')).toBe(false);
|
||||
expect(function() {
|
||||
te.isActive('b');
|
||||
}).toThrowError('Error accessing target "b". It is not in the list of allowed targets: a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('activate', function() {
|
||||
it('should active an already allowed target', function() {
|
||||
te.addAllowed('a', true);
|
||||
te.addAllowed('b', false);
|
||||
te.addAllowed('c');
|
||||
|
||||
te.activate('a');
|
||||
te.activate('b');
|
||||
te.activate('c');
|
||||
expect(te.isActive('a')).toBe(true);
|
||||
expect(te.isActive('b')).toBe(true);
|
||||
expect(te.isActive('c')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deactivate', function() {
|
||||
it('should deactive an already allowed target', function() {
|
||||
te.addAllowed('a', true);
|
||||
te.addAllowed('b', false);
|
||||
te.addAllowed('c');
|
||||
|
||||
te.deactivate('a');
|
||||
te.deactivate('b');
|
||||
te.deactivate('c');
|
||||
expect(te.isActive('a')).toBe(false);
|
||||
expect(te.isActive('b')).toBe(false);
|
||||
expect(te.isActive('c')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isActive', function() {
|
||||
it('should return true if the item is allowed and active', function() {
|
||||
te.addAllowed('a', true);
|
||||
te.addAllowed('b', false);
|
||||
|
||||
expect(te.isActive('a')).toBe(true);
|
||||
expect(te.isActive('b')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasActive', function() {
|
||||
it('should return true if there are any active targets', function() {
|
||||
te.addAllowed('a', true);
|
||||
te.addAllowed('b', false);
|
||||
expect(te.hasActive()).toBe(true);
|
||||
|
||||
te.deactivate('a');
|
||||
expect(te.hasActive()).toBe(false);
|
||||
|
||||
te.activate('b');
|
||||
expect(te.hasActive()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('someActive', function() {
|
||||
it('should return true if the array of targets passed are all allowed and at least on is active',
|
||||
function() {
|
||||
te.addAllowed('a', true);
|
||||
te.addAllowed('b', false);
|
||||
te.addAllowed('c');
|
||||
|
||||
expect(te.someActive(['a'])).toBe(true);
|
||||
expect(te.someActive(['b'])).toBe(false);
|
||||
expect(te.someActive(['a', 'b'])).toBe(true);
|
||||
expect(te.someActive(['b', 'c'])).toBe(false);
|
||||
expect(te.someActive([])).toBe(false);
|
||||
|
||||
expect(function() { te.someActive('d'); })
|
||||
.toThrowError(
|
||||
'Error accessing target "d". It is not in the list of allowed targets: a,b,c');
|
||||
});
|
||||
});
|
||||
});
|
@ -21,6 +21,7 @@
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"typings-test",
|
||||
"public_api_guard"
|
||||
"public_api_guard",
|
||||
"docs"
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user