Skip to content

Commit

Permalink
move to Expressions class
Browse files Browse the repository at this point in the history
  • Loading branch information
kla committed Sep 17, 2024
1 parent 9c76ac9 commit aeb0e93
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 52 deletions.
66 changes: 66 additions & 0 deletions src/staxfile/expressions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { existsSync } from 'fs'
import { dasherize } from '~/utils'
import * as path from 'path'
import Staxfile from './index'

export default class Expressions {
private staxfile: Staxfile

constructor(staxfile: Staxfile) {
this.staxfile = staxfile
}

evaluate(name: string, args: any[]): string {
if (name.startsWith('stax.')) return this.fetchConfigValue(name)
if (name === 'read') return this.read(args[0], args[1])
if (name === 'mount_workspace') return this.mountWorkspace()
if (name === 'mount_ssh_auth_sock') return this.mountSshAuthSock()
if (name === 'path.resolve') return path.resolve(args[0])
if (name === 'user') return process.env.USER || ''
if (name === 'user_id') return process.getuid().toString()
if (name === 'dasherize') return dasherize(args[0])
if (name === 'exists') return existsSync(args[0]).toString()

this.staxfile.warnings.add(`Invalid template expression: ${name}`)
}

private fetchConfigValue(name: string): string {
const key = name.slice(5) // strip 'stax.' prefix

if (key === 'host_services')
return process.env.STAX_HOST_SERVICES || ''

if (key === 'ssh_auth_sock')
return '/run/host-services/ssh-auth.sock'

if (!this.staxfile.config.hasProperty(key)) {
if (name === 'config.workspace_volume' && !this.staxfile.location.local)
this.staxfile.warnings.add(`A '${name}' name must be defined when setting up from a remote source.`)

this.staxfile.warnings.add(`Undefined reference to '${name}'`)
}

return this.staxfile.config.fetch(key)
}

private read(file: string, defaultValue: string): string {
try {
return (this.staxfile.location.readSync(file) || defaultValue).trim()
} catch (e) {
console.warn(`Couldn't read ${file}: ${e.code}... using default value of '${defaultValue}'`)
return defaultValue
}
}

private mountWorkspace(): string {
const src = this.staxfile.config.location.local ? this.staxfile.config.source : this.staxfile.config.workspace_volume
const dest = this.staxfile.config.workspace
return `${src}:${dest}`
}

private mountSshAuthSock(): string {
return process.platform === 'darwin' ?
'${{ stax.ssh_auth_sock }}:${{ stax.ssh_auth_sock }}' :
'${{ stax.host_services }}:/run/host-services'
}
}
56 changes: 4 additions & 52 deletions src/staxfile/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from 'fs'
import { cacheDir as _cacheDir, dasherize, exit, flattenObject, getNonNullProperties, verifyFile } from '~/utils'
import { cacheDir as _cacheDir, exit, flattenObject, getNonNullProperties, verifyFile } from '~/utils'
import { StaxConfig } from '~/types'
import { renderTemplate } from './template'
import Config from './config'
import DockerfileCompiler from './dockerfile_compiler'
import Expressions from './expressions'
import Location from '~/location'
import icons from '~/icons'
import * as path from 'path'
Expand All @@ -12,8 +13,8 @@ import yaml from 'js-yaml'
export default class Staxfile {
public config: Config
public compose: Record<string, any>
public warnings: Set<string>
private buildsCompiled: Record<string, string> = {}
private warnings: Set<string>

constructor(config: StaxConfig) {
this.config = new Config(config)
Expand Down Expand Up @@ -92,61 +93,12 @@ export default class Staxfile {

text = renderTemplate(text, (name, args) => {
matches += 1

if (name.startsWith('stax.')) return this.fetchConfigValue(name)
else if (name === 'read') return this.read(args[0], args[1])
else if (name == 'mount_workspace') return this.mountWorkspace()
else if (name == 'mount_ssh_auth_sock') return this.mountSshAuthSock()
else if (name == 'path.resolve') return path.resolve(args[0])
else if (name == 'user') return process.env.USER
else if (name == 'user_id') return process.getuid()
else if (name == 'dasherize') return dasherize(args[0])
else this.warnings.add(`Invalid template expression: ${name}`)
return new Expressions(this).evaluate(name, args)
})

return matches > 0 ? this.render(text) : text
}

private read(file, defaultValue) {
try {
return (this.location.readSync(file) || defaultValue).trim()
} catch (e) {
console.warn(`${icons.warning} Couldn't read ${file}: ${e.code}... using default value of '${defaultValue}'`)
return defaultValue
}
}

private mountWorkspace() {
const src = this.config.location.local ? this.config.source : this.config.workspace_volume
const dest = this.config.workspace
return `${src}:${dest}`
}

private mountSshAuthSock() {
return process.platform === 'darwin' ?
'${{ stax.ssh_auth_sock }}:${{ stax.ssh_auth_sock }}' :
'${{ stax.host_services }}:/run/host-services'
}

private fetchConfigValue(name) {
const key = name.slice(5) // strip 'stax.' prefix

if (key == 'host_services')
return process.env.STAX_HOST_SERVICES

if (key == 'ssh_auth_sock')
return '/run/host-services/ssh-auth.sock'

if (!this.config.hasProperty(key)) {
if (name == 'config.workspace_volume' && !this.location.local)
this.warnings.add(`A '${name}' name must be defined when setting up from a remote source.`)

this.warnings.add(`Undefined reference to '${name}'`)
}

return this.config.fetch(key)
}

private updateServices() {
const services = {}
let number = 0
Expand Down
88 changes: 88 additions & 0 deletions tests/unit/staxfile/expressions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { describe, it, expect, beforeEach, mock } from 'bun:test'
import Expressions from '~/staxfile/expressions'

describe('Expressions', () => {
let expression
let mockStaxfile

beforeEach(() => {
mockStaxfile = {
warnings: { add: mock(() => {}) },
config: {
hasProperty: mock(() => true),
fetch: mock((key) => `mock_${key}`),
},
location: {
local: true,
readSync: mock(() => 'mock_file_content'),
},
}
expression = new Expressions(mockStaxfile)
})

it('evaluates stax config values', () => {
const result = expression.evaluate('stax.some_key', [])
expect(result).toBe('mock_some_key')
expect(mockStaxfile.config.fetch).toHaveBeenCalledWith('some_key')
})

it('evaluates read function', () => {
const result = expression.evaluate('read', ['test.txt', 'default'])
expect(result).toBe('mock_file_content')
expect(mockStaxfile.location.readSync).toHaveBeenCalledWith('test.txt')
})

// it('evaluates mount_workspace function', () => {
// mockStaxfile.config.source = '/mock/source'
// mockStaxfile.config.workspace = '/mock/workspace'
// const result = expression.evaluate('mount_workspace', [])
// expect(result).toBe('/mock/source:/mock/workspace')
// })

it('evaluates mount_ssh_auth_sock function', () => {
const originalPlatform = process.platform
Object.defineProperty(process, 'platform', { value: 'darwin' })
const result = expression.evaluate('mount_ssh_auth_sock', [])
expect(result).toBe('${{ stax.ssh_auth_sock }}:${{ stax.ssh_auth_sock }}')
Object.defineProperty(process, 'platform', { value: originalPlatform })
})

it('evaluates path.resolve function', () => {
const result = expression.evaluate('path.resolve', ['/test/path'])
expect(result).toBe('/test/path')
})

it('evaluates user function', () => {
const originalUser = process.env.USER
process.env.USER = 'testuser'
const result = expression.evaluate('user', [])
expect(result).toBe('testuser')
process.env.USER = originalUser
})

it('evaluates user_id function', () => {
const result = expression.evaluate('user_id', [])
expect(result).toBe(process.getuid().toString())
})

it('evaluates dasherize function', () => {
const result = expression.evaluate('dasherize', ['TestString'])
expect(result).toBe('test-string')
})

// it('evaluates exists function', () => {
// const result = expression.evaluate('exists', ['/existing/path'])
// expect(result).toBe('true')
// })

it('adds warning for invalid expression', () => {
expression.evaluate('invalid_expression', [])
expect(mockStaxfile.warnings.add).toHaveBeenCalledWith('Invalid template expression: invalid_expression')
})

it('adds warning for undefined stax config', () => {
mockStaxfile.config.hasProperty = mock(() => false)
expression.evaluate('stax.undefined_key', [])
expect(mockStaxfile.warnings.add).toHaveBeenCalledWith("Undefined reference to 'stax.undefined_key'")
})
})

0 comments on commit aeb0e93

Please sign in to comment.