Skip to content

Instantly share code, notes, and snippets.

@kohlmannj
Created November 1, 2019 22:57
Show Gist options
  • Save kohlmannj/8edc891833132360ea5d11bfd1b67b15 to your computer and use it in GitHub Desktop.
Save kohlmannj/8edc891833132360ea5d11bfd1b67b15 to your computer and use it in GitHub Desktop.
Sketch of a TypeScript version of https://npm.im/opml-generator
/* 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