@sap/cds-dk
Version:
Command line client and development toolkit for the SAP Cloud Application Programming Model
227 lines (183 loc) • 7.02 kB
JavaScript
/* USAGE:
cds inspect model.Req
cds inspect model.Requisition --ids
*/
module.exports = Object.assign (inspect, {
options: [ '--stats', '--list', '--kind' ],
shortcuts: [ '-s', '-l', '-k' ],
help: `
# SYNOPSIS
*cds inspect* [ <options> ]
Inspects the cds models of a project, or a specific definition.
Without arguments shows --stats a summary of your models.
You can list all definitions, and thereby filter the listed definitions
by kind, and/or a name pattern, using wildcards like * and ?.
For a specific definition you can list its elements, keys, associations,
compositions, or IDs using --list. It also shows where the definition
is inherited from, and counts of its elements.
See examples below.
# OPTIONS
*-s | --stats*
Shows a summary of the kinds of definitions found.
*-l | --list* <names|elements|keys|associations|compositions|ids>
Lists the specified kind of elements of a specific definition.
*-k | --kind* <entity|type|service|...>
Filters the listed definitions by kind.
# EXAMPLES
*cds inspect*
*cds inspect* --stats
*cds inspect* --kind aspect
*cds inspect* --list all
*cds inspect* --list names --kind entity
*cds inspect* --list names model.Req*
*cds inspect* requisition.model.Requisition
*cds inspect* requisition.model.Requisition --list elements
*cds inspect* requisition.model.Requisition --list ids
*cds inspect* requisition.model.Requisition --list keys
*cds inspect* requisition.model.Requisition --list associations
*cds inspect* requisition.model.Requisition --list compositions
`})
async function inspect(){
const cds = require ('../lib/cds')
const path = require ('node:path')
const { YELLOW, GREEN, CYAN, RED, GRAY, RESET } = cds.utils.colors
const i2 = ' '.repeat(2)
const i4 = ' '.repeat(4)
const args = process.argv.slice(3)
const option = (...oo) => {
for (let o of oo) {
const i = args.indexOf(o)
if (i>=0) return args.splice(i,2)[1] || ''
}
}
const fetch = { __proto__: {
get 'id elements' () { return this.ids },
},
names: () => true,
elements: () => true,
ids: e => e.name.match(/(ID|Id|id$)/),
keys: e => e.key,
associations: a => a.isAssociation && !a.isComposition,
compositions: a => a.isComposition,
'to-many': a => a.is2many,
'to-one': a => a.is2one,
unmanaged: a => a.is2one && a.on,
virtuals: e => e.virtual,
calculated: e => e.value,
}
const $location = d => {
if (Object.hasOwn(d,'$location')) return d.$location.file+':'+d.$location.line
let includes = d.parent?.includes; if (!includes) return ''
for (let inc of includes) {
let e = csn.definitions[inc]?.elements?.[d.name]
if (e) return $location(e)
}
}
const csn = await cds.load('*').then(cds.linked)
let defs = Object.values (csn.definitions)
const kinds = {
entities: 'entity',
aspects: 'aspect',
types: 'type',
services: 'service',
events: 'event',
actions: 'action',
functions: 'function',
contexts: 'context',
all: undefined,
}
let kind = option ('-k','--kind')
let list = option ('-l','--list')
let stats = option ('-s','--stats')
if (list in kinds) [kind,list] = [kinds[list],'names']
if (kind) defs = defs.filter (d => d.kind === kind)
if (process.argv.length < 4) stats = true
const regexp4 = pattern => RegExp (pattern.replace(/\./g, '\\.').replace(/\*/g, '.*'))
for (let pattern of args) {
const regexp = regexp4(pattern || '.*')
defs = defs.filter (d => regexp.test(d.name))
}
console.log()
if (stats !== undefined) {
const count = (kind,f) => {
let n = defs.filter(f).length; if (!n) return
if (kind === 'entities') for (let each of ['.texts','.drafts']) {
let n = defs.filter(d => d.name.endsWith(each)).length
if (n) kind += ` - ${YELLOW}${n}${GRAY} ${each}`
}
if (n) console.log (YELLOW, n.toString().padStart(4,' '), GRAY + kind)
}
for (let k in kinds) count (k, d => d.kind === kinds[k])
console.log (GRAY, '-'.repeat(14), RESET)
console.log (YELLOW, defs.length.toString().padStart(4,' '), GRAY + 'in total')
} else {
defs = defs.filter (d => !d.name.match(/\.(texts|drafts)$/))
switch (list) {
case 'names':
const pad = kind?.length || 7 // eslint-disable-line no-case-declarations
defs.forEach (d => console.log (i2, RED + d.kind.padEnd(pad), GREEN + d.name))
console.log(GRAY+i2, '-'.repeat(27), RESET)
console.log(GRAY+i2, 'Total:', YELLOW + defs.length, GRAY + 'definitions')
break;
case 'sources':
csn.$sources.forEach(f => console.log (GRAY+i2, path.relative(cds.root,f)))
break;
default:
defs.forEach (inspect)
}
}
console.log(RESET)
function inspect (d) {
console.log(GRAY+i2, '-'.repeat(79), RESET)
console.log(GRAY+i2, 'Inspecting', RED + d.kind, GREEN + d.name, RESET)
console.log(GRAY+i2, '( declared at'+ CYAN, $location(d), GRAY+')', RESET)
console.log()
if (list !== undefined) {
const filter = fetch [list]
if (!filter) return console.error (RED, 'ERROR:'+ RESET, 'Use --list with one of:\n\n ', Object.keys(fetch).join('\n '), RESET)
const elements = Object.values(d.elements??{}).filter(filter)
const pad = elements.reduce((p, e) => Math.max(p, e.name.length), 0)
for (let e of elements) {
console.log (i4, GRAY + '-', YELLOW + e.name.padEnd(pad), GRAY, $location(e))
}
return console.log()
}
count(d.elements, 'keys')
count(d.elements, 'id elements')
count(d.elements, 'elements')
count(d.elements, 'associations', 'to-many', 'unmanaged')
count(d.elements, 'compositions', 'to-many')
count(d.elements, 'virtuals')
count(d.elements, 'calculated')
if (d.includes) {
console.log(RESET + '\n', i2, 'inherits: \n')
inheritance(d)
}
console.log()
}
function inheritance (d, indent = '') {
if (d.includes) for (let each of d.includes) {
let d = csn.definitions[each]
console.log(i4, ' -', count(d.elements), GRAY + 'elements from' + indent, RED + d.kind, GREEN + d.name, RESET)
inheritance(d, indent + i2)
}
}
function count (defs, kind, ...more) {
if (!defs) return
if (!Array.isArray(defs)) defs = Object.values(defs)
if (kind) {
const filter = fetch[kind]
if (filter) defs = defs.filter(filter)
if (!defs.length) return
}
let info = YELLOW+ defs.length.toString().padStart(3, ' ')
if (kind) info += GRAY + ' ' + kind; else return info
for (let kind of more) {
let filter = fetch[kind] || fetch[kind.replace(/-/g,'_')]
let n = defs.filter(filter).length
if (n) info += ', '+ YELLOW + defs.filter(filter).length + GRAY+' ' + kind
}
console.log (i4, info, GRAY)
}
}