Created
November 1, 2019 22:57
-
-
Save kohlmannj/8edc891833132360ea5d11bfd1b67b15 to your computer and use it in GitHub Desktop.
Sketch of a TypeScript version of https://npm.im/opml-generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-underscore-dangle */ | |
import xml from 'xml' | |
/** | |
* Created by azu on 2014/01/18. | |
* LICENSE : MIT | |
* @see https://github.com/azu/opml-generator | |
*/ | |
/** | |
* @see http://dev.opml.org/spec2.html#whatIsALtheadgt | |
*/ | |
export interface Header { | |
/** | |
* A date-time, indicating when the document was created. | |
*/ | |
dateCreated?: Date | string | |
/** | |
* A date-time, indicating when the document was last modified. | |
*/ | |
dateModified?: Date | string | |
/** | |
* The http address of documentation for the format used in the OPML file. It's probably a | |
* pointer to this page for people who might stumble across the file on a web server 25 years | |
* from now and wonder what it is. | |
*/ | |
docs?: string | |
/** | |
* A comma-separated list of line numbers that are expanded. The line numbers in the list tell | |
* you which headlines to expand. The order is important. For each element in the list, X, | |
* starting at the first summit, navigate flatdown X times and expand. Repeat for each element | |
* in the list. | |
*/ | |
expansionState?: string | number[] | |
/** | |
* The email address of the owner of the document. | |
*/ | |
ownerEmail?: string | |
/** | |
* The http address of a web page that contains information that allows a human reader to | |
* communicate with the author of the document via email or other means. It also may be used to | |
* identify the author. No two authors have the same `ownerId`. | |
*/ | |
ownerId?: string | |
/** | |
* The owner of the document. | |
*/ | |
ownerName?: string | |
/** | |
* The title of the document. | |
*/ | |
title?: string | |
/** | |
* A number, saying which line of the outline is displayed on the top line of the window. | |
* This number is calculated with the expansion state already applied. | |
*/ | |
vertScrollState?: number | |
/** | |
* The pixel location of the top edge of the window. | |
*/ | |
windowTop?: number | |
/** | |
* The pixel location of the left edge of the window. | |
*/ | |
windowLeft?: number | |
/** | |
* The pixel location of the bottom edge of the window. | |
*/ | |
windowBottom?: number | |
/** | |
* The pixel location of the right edge of the window. | |
*/ | |
windowRight?: number | |
} | |
/** | |
* @see http://dev.opml.org/spec2.html#whatIsAnLtoutlinegt | |
*/ | |
export type Outline<T, ChildrenT = unknown> = T & { | |
text: string | |
type?: string | |
/** | |
* A string, either `"true"` or `"false"`, indicating whether the outline is commented or not. | |
* By convention if an outline is commented, all subordinate outlines are considered to also | |
* be commented. If it's not present, the value is `false`. | |
*/ | |
isComment?: boolean | 'true' | 'false' | |
/** | |
* A string, either "true" or "false", indicating whether a breakpoint is set on this outline. | |
* This attribute is mainly necessary for outlines used to edit scripts. If it's not present, | |
* the value is `false`. | |
*/ | |
isBreakpoint?: boolean | 'true' | 'false' | |
/** | |
* The date-time that the outline node was created. | |
*/ | |
created?: Date | string | |
/** | |
* A string of comma-separated slash-delimited category strings, in the format defined by the | |
* [RSS 2.0 category](http://cyber.law.harvard.edu/rss/rss.html#ltcategorygtSubelementOfLtitemgt) | |
* element. To represent a "tag," the category string should contain no slashes. | |
* | |
* Examples: | |
* 1. `category="/Boston/Weather"`. | |
* 2. `category="/Harvard/Berkman,/Politics"`. | |
*/ | |
category?: string | string[] | |
/** | |
* Zero or more `Outline<T>` sub-elements. | |
*/ | |
_children: ChildrenT extends Outline<infer U>[] ? Outline<U>[] : undefined | |
} | |
// type OutlineChildType<T> = T extends Outline<infer U, infer V> ? V : never | |
const o: Outline<{ foo: 'bar' }> = { | |
text: 'asdf', | |
foo: 'bar', | |
_children: [ | |
{ | |
text: 'foo', | |
// foo: 'bar', | |
bar: 'baz', | |
}, | |
], | |
} | |
// type X = typeof o extends Outline<unknown> | |
// type Q = (typeof o)['_children'] | |
// type Z = OutlineChildType<typeof o> | |
type XmlFormattedOutline<T extends {}, V extends Outline<T>[] | undefined> = Array<{ | |
outline: V extends Outline<T>[] | |
? (XmlFormattedOutline<T, V> | { _attr: Omit<Outline<T>, '_children'> })[] | |
: [{ _attr: Omit<Outline<T>, '_children'> }] | |
}> | |
export function createOutlines<T extends {}, V extends Outline<T>[] | undefined>( | |
outlines?: V, | |
): XmlFormattedOutline<T, V> { | |
if (!outlines) { | |
return [] | |
} | |
return outlines.map(({ _children, ..._attr }) => ({ | |
outline: [...createOutlines(_children), { _attr }], | |
})) | |
} | |
export function createBody<T extends {}>(outlines: Outline<T>[]): string { | |
return xml({ body: createOutlines(outlines) }) | |
} | |
createBody([o]) | |
export function createHeader({ | |
dateCreated, | |
dateModified, | |
expansionState, | |
...rest | |
}: Header): string { | |
const headerObject = { | |
...rest, | |
dateCreated: dateCreated instanceof Date ? dateCreated.toUTCString() : dateCreated, | |
dateModified: dateModified instanceof Date ? dateModified.toUTCString() : dateModified, | |
expansionState: Array.isArray(expansionState) ? expansionState.join(',') : expansionState, | |
} | |
return xml({ | |
head: headerObject, | |
}) | |
} | |
/** | |
* | |
* @param header | |
* @param outlines | |
*/ | |
export default function opmlGenerator<T extends {}>( | |
header: Header, | |
outlines: Outline<T>[], | |
): string { | |
const headerXML = createHeader(header) | |
const outlinesXML = createBody(outlines) | |
return `<?xml version="1.0" encoding="UTF-8"?><opml version="2.0">${headerXML}${outlinesXML}</opml>` | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment