Skip to content

Instantly share code, notes, and snippets.

@VanTanev
Last active June 29, 2020 11:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save VanTanev/8a6ea41257eba917241cb026e5f52240 to your computer and use it in GitHub Desktop.
Save VanTanev/8a6ea41257eba917241cb026e5f52240 to your computer and use it in GitHub Desktop.
Extract static type strings from io-ts definitions
import * as t from 'io-ts'
import * as gen from 'io-ts-codegen'
import { NullableType } from 'codecs/util/nullable'
export function stringifyNaive(identifier: string, codec: t.Any): string {
return `type ${identifier} = ${codec.name}`
}
export function stringifyFull(identifier: string, codec: t.Any): string {
return `type ${identifier} = ${gen.printStatic(getType(codec))}`
}
function getType(codec: t.Any): gen.TypeReference {
switch ((codec as any)._tag) {
case 'StringType':
return gen.stringType
case 'NumberType':
return gen.numberType
case 'BooleanType':
return gen.booleanType
case 'NullType':
return gen.nullType
case 'UndefinedType':
return gen.undefinedType
case 'UnknownType':
return gen.unknownType
case 'InterfaceType':
return generateInterfaceType(codec as any)
case 'ArrayType':
return generateArrayType(codec as any)
case 'NullableType':
return generateNullableType(codec as any)
case 'KeyofType':
return generateKeyofType(codec as any)
case 'ExactType':
return generateInterfaceType((codec as any).type)
case 'UnionType':
return generateUnionType(codec as any)
}
throw Error(`Cannot extract type for ${codec.name}: ${(codec as any)._tag}`)
}
function generateInterfaceType(codec: t.TypeC<t.Props>): gen.TypeReference {
return gen.typeCombinator(
Object.keys(codec.props).map(key => gen.property(key, getType(codec.props[key]))),
)
}
function generateArrayType(codec: t.ArrayType<t.Any>): gen.TypeReference {
return gen.arrayCombinator(getType(codec.type))
}
function generateKeyofType(codec: t.KeyofType<any>): gen.TypeReference {
return gen.keyofCombinator(Object.keys(codec.keys))
}
function generateNullableType(codec: NullableType<any>): gen.TypeReference {
return gen.unionCombinator([getType(codec.type), gen.nullType])
}
function generateUnionType(codec: t.UnionType<any, any, any>): gen.TypeReference {
return gen.unionCombinator(codec.types.map(getType))
}
import * as FS from 'fs'
import * as Path from 'path'
import * as t from 'io-ts'
import * as prettier from 'prettier'
import * as rimraf from 'rimraf'
import * as ts from './io-ts-stringify'
import { CastMemberCodec } from 'codecs/CastMember'
import { CharacterCodec, CharacterWithPortrayalCodec } from 'codecs/Character'
import { RelationshipCodec } from 'codecs/Relationship'
import { SeasonCodec } from 'codecs/Season'
import { EpisodeCodec } from 'codecs/Episode'
import { FeatureCodec } from 'codecs/Feature'
import { SeriesCodec } from 'codecs/Series'
import { FeatureStorylineCodec } from 'codecs/storyline/FeatureStoryline'
import { EpisodeStorylineCodec } from 'codecs/storyline/EpisodeStoryline'
import { StoryRoleCodec } from 'codecs/storyline/StoryRole'
import { SearchResultCodec } from 'codecs/SearchResult'
import { LockCodec } from 'codecs/Lock'
const ARGV = process.argv.slice(2)
const OUTPUT_DIR = ARGV[0] ?? Path.join(process.cwd(), 'docs', 'types')
ensureEnv()
writeDocfile(
'README.md',
`# Docs
### [Main Types](./main_types.md)
These contain the main title types used in the system
### [Component Types](./component_types.md)
These are named types that are used inside main types. Things like Storyline, for example.
### [API Types](./api_types.md)
These are API specific types that do not strictly depend on the SPARQL database.
`,
)
writeTypesDocfile('main_types.md', [
['Feature', FeatureCodec],
['Series', SeriesCodec],
['Episode', EpisodeCodec],
['Season', SeasonCodec],
])
writeTypesDocfile('component_types.md', [
['FeatureStoryline', FeatureStorylineCodec, 'Feature Storyline'],
['EpisodeStoryline', EpisodeStorylineCodec, 'Episode Storyline'],
['StoryRole', StoryRoleCodec, 'Story Role'],
['Character', CharacterCodec],
['CharacterWithPortrayal', CharacterWithPortrayalCodec, 'Character With Portrayal'],
['CastMember', CastMemberCodec, 'Cast Member'],
['Relationship', RelationshipCodec],
])
writeTypesDocfile('api_types.md', [
['SearchResult', SearchResultCodec, 'Search Result', ts.stringifyFull],
['Lock', LockCodec],
])
////////////////////////////////////////
function writeTypesDocfile(
filename: string,
defs: Array<[string, t.Any, string?, ((identifer: string, codec: t.Any) => string)?]>,
): void {
writeDocfile(
filename,
defs
.map(([identifier, codec, label, stringify = ts.stringifyNaive]) => {
let definition = stringify(identifier, codec)
let formatted = prettier.format(definition, {
parser: 'babel-ts',
semi: false,
tabWidth: 4,
})
let markdown = [
`# ${label ?? identifier}`,
'',
'```typescript',
formatted,
'```',
].join('\n')
return markdown
})
.join('\n\n---\n\n'),
)
}
function writeDocfile(filename: string, contents: string): void {
FS.writeFileSync(Path.join(OUTPUT_DIR, filename), contents)
}
function ensureEnv(): void {
clearOutputDir()
createOutputDir()
}
function createOutputDir(): void {
FS.mkdirSync(OUTPUT_DIR, { recursive: true })
}
function clearOutputDir(): void {
rimraf.sync(OUTPUT_DIR)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment