Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.3.0 #11

Merged
merged 5 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "binspector",
"description": "A _truly_ declarative library for binary file and protocol definition written in typescript. Read binary files based on class definition and decorators directly from your webapp.",
"version": "0.2.1",
"version": "0.3.0",
"main": "dist/binspector.js",
"types": "dist/index.d.ts",
"files": [
Expand Down
8 changes: 4 additions & 4 deletions src/__tests__/writer.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect } from '@jest/globals'
import { Bitfield, Relation, Choice, Count, Matrix, Peek, Offset, Endian, NullTerminatedString, TransformScale, TransformOffset, TransformerExecutionScope, Transform, Until } from '../decorators'
import { InstantiableObject, PrimitiveSymbol, EOF } from '../types'
import { Bitfield, Relation, Choice, Count, Matrix, Peek, Offset, Endian, NullTerminatedString, TransformScale, TransformOffset, Transform, Until } from '../decorators'
import { ExecutionScope, InstantiableObject, PrimitiveSymbol, EOF } from '../types'
import { binwrite } from '../writer'
import { binread } from '../reader'
import { BinaryReader, BinaryWriter, BinaryCursorEndianness } from '../cursor'
Expand Down Expand Up @@ -305,11 +305,11 @@ describe('Writing binary definition with Transformer decorators', () => {
@Transform((value: number[]) => {
const buf = new Uint8Array(value)
return new TextDecoder().decode(buf)
}, { scope: TransformerExecutionScope.OnRead })
}, { scope: ExecutionScope.OnRead })
@Transform((value: string) => {
const buf = new TextEncoder().encode(value)
return Array.from(buf)
}, { scope: TransformerExecutionScope.OnWrite })
}, { scope: ExecutionScope.OnWrite })
@Until(EOF)
@Relation(PrimitiveSymbol.u8)
decodedString: string
Expand Down
17 changes: 0 additions & 17 deletions src/decorators/__tests__/condition.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { describe, expect } from '@jest/globals'
import { useConditions, IfThen, Else, Choice } from '../condition'
import { type RelationTypeProperty, type PrimitiveTypeProperty } from '../primitive'
import { NoConditionMatched } from '../../error'
import Meta from '../../metadatas'

function testCondition<This> (TargetClass: new (...args: any) => This, relation: any, post?: (relation: PrimitiveTypeProperty<This> | RelationTypeProperty<This, any> | undefined, instance: This) => void, field: keyof This = 'field' as keyof This) {
Expand Down Expand Up @@ -119,19 +118,3 @@ describe('@Condition: basic testing', () => {
})
})
})

describe('@Condition: error testing', () => {
it('NoConditionMatched: should alway define a valid option', () => {
class TestClass {
@IfThen(_ => false, Number)
field: number
}

const instance = new TestClass()
const conditions = Meta.getConditions(TestClass[Symbol.metadata] as DecoratorMetadataObject, 'field')

expect(() => {
useConditions(conditions, instance)
}).toThrow(NoConditionMatched)
})
})
5 changes: 4 additions & 1 deletion src/decorators/condition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,10 @@ export function useConditions<This, Value> (conditions: Array<Condition<This>>,
// - cursor backtrace to show the position in the file
// - current instance to print. The condition are mostly done on the current instance
// - If creating a `@Choice` it's probably more easy to debug.
throw new NoConditionMatched()
// It's still unclear if I need to throw an error or not. Right now usecases
// seems to indicate it's better to not throw.
// throw new NoConditionMatched()
return undefined
}

return cond.relation
Expand Down
14 changes: 11 additions & 3 deletions src/decorators/prepost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
*/
import { ClassMetaDescriptor, type PropertyMetaDescriptor, createClassMetaDescriptor, createPropertyMetaDescriptor, recursiveGet } from './common'
import { relationExistsOrThrow } from '../error'
import { type ClassAndPropertyDecoratorType, type ClassAndPropertyDecoratorContext } from '../types'
import { ExecutionScope, type ClassAndPropertyDecoratorType, type ClassAndPropertyDecoratorContext } from '../types'
import { type Cursor, type BinaryCursorEndianness, BinaryCursor } from '../cursor'
import Meta from '../metadatas'

Expand All @@ -73,11 +73,17 @@ export interface PrePostOptions {
* Removes the decorator from metadata after its function is executed.
*/
once: boolean
/**
* Specifies whether the prepost function should be executed during
* the read phase, the write phase, or both.
*/
scope: ExecutionScope
}

export const PrePostOptionsDefault = {
primitiveCheck: true,
once: false,
scope: ExecutionScope.OnBoth,
}

/**
Expand Down Expand Up @@ -632,8 +638,10 @@ export function Endian<This> (endianness: BinaryCursorEndianness | ((instance: T
*
* @category Advanced Use
*/
export function usePrePost<This> (prepost: Array<PrePost<This>> | Array<PrePostClass<This>>, targetInstance: This, cursor: Cursor): void {
export function usePrePost<This> (prepost: Array<PrePost<This>> | Array<PrePostClass<This>>, targetInstance: This, cursor: Cursor, scope = ExecutionScope.OnBoth): void {
prepost.forEach((x) => {
x.func(targetInstance, cursor)
if ((x.options.scope & scope) > 0) {
x.func(targetInstance, cursor)
}
})
}
32 changes: 11 additions & 21 deletions src/decorators/transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
*
* By default, custom transformers are applied only during the reading phase.
* To support binary encoding (writing phase), define an additional transformer
* with a {@link TransformerExecutionScope} set to `OnWrite` or `OnBoth` via
* with a {@link ExecutionScope} set to `OnWrite` or `OnBoth` via
* {@link TransformerOptions}.
*
* The {@link Transformer} category define various decorators to perform
Expand All @@ -49,21 +49,11 @@
*/
import { createPropertyMetaDescriptor, type PropertyMetaDescriptor } from './common'
import { relationExistsOrThrow } from '../error'
import { type DecoratorType, type Context } from '../types'
import { ExecutionScope, type DecoratorType, type Context } from '../types'
import Meta from '../metadatas'

export const TransformerSymbol = Symbol('transformer')

/**
* The execution scope defines in which part of the binary processing (read,
* write or both) the transformer is applied.
*/
export enum TransformerExecutionScope {
OnRead = 0x01,
OnWrite = 0x02,
OnBoth = 0x03,
}

/**
* TransformerOptions.
*/
Expand All @@ -80,13 +70,13 @@ export interface TransformerOptions {
* Specifies whether the transformer function should be executed during
* the read phase, the write phase, or both.
*/
scope: TransformerExecutionScope
scope: ExecutionScope
}

export const TransformerOptionsDefault = {
primitiveCheck: true,
each: false,
scope: TransformerExecutionScope.OnRead,
scope: ExecutionScope.OnRead,
}

/**
Expand Down Expand Up @@ -187,11 +177,11 @@ export function transformerDecoratorFactory<This, Value> (name: string, func: Tr
* @Transform((value: number[]) => {
* const buf = new Uint8Array(value)
* return new TextDecoder().decode(buf)
* }, { scope: TransformerExecutionScope.OnRead })
* }, { scope: ExecutionScope.OnRead })
* @Transform((value: string) => {
* const buf = new TextEncoder().encode(value)
* return Array.from(buf)
* }, { scope: TransformerExecutionScope.OnWrite })
* }, { scope: ExecutionScope.OnWrite })
* @Until(EOF)
* @Relation(PrimitiveSymbol.u8)
* decodedString: string;
Expand Down Expand Up @@ -237,8 +227,8 @@ export function Transform<This, Value> (transformFunction: TransformerFunction<T
*/
export function TransformScale<This, Value> (scale: number, opt?: Partial<TransformerOptions>): DecoratorType<This, Value> {
return function (_: any, context: Context<This, Value>) {
transformerDecoratorFactory('transform-scale', x => x * scale, opt)(_, context)
transformerDecoratorFactory('transform-scale', x => x / scale, { ...opt, scope: TransformerExecutionScope.OnWrite })(_, context)
transformerDecoratorFactory('transform-scale', x => x * scale, { ...opt, each: true })(_, context)
transformerDecoratorFactory('transform-scale', x => x / scale, { ...opt, each: true, scope: ExecutionScope.OnWrite })(_, context)
}
}

Expand All @@ -259,8 +249,8 @@ export function TransformScale<This, Value> (scale: number, opt?: Partial<Transf
*/
export function TransformOffset<This, Value> (off: number, opt?: Partial<TransformerOptions>): DecoratorType<This, Value> {
return function (_: any, context: Context<This, Value>) {
transformerDecoratorFactory('transform-offset', x => x + off, opt)(_, context)
transformerDecoratorFactory('transform-offset', x => x - off, { ...opt, scope: TransformerExecutionScope.OnWrite })(_, context)
transformerDecoratorFactory('transform-offset', x => x + off, { ...opt, each: true })(_, context)
transformerDecoratorFactory('transform-offset', x => x - off, { ...opt, each: true, scope: ExecutionScope.OnWrite })(_, context)
}
}

Expand All @@ -277,7 +267,7 @@ export function TransformOffset<This, Value> (off: number, opt?: Partial<Transfo
*
* @category Advanced Use
*/
export function useTransformer<This> (transformers: Array<Transformer<This>>, propertyValue: any, targetInstance: This, scope = TransformerExecutionScope.OnRead): any {
export function useTransformer<This> (transformers: Array<Transformer<This>>, propertyValue: any, targetInstance: This, scope = ExecutionScope.OnRead): any {
return transformers.reduce((transformedTmpValue, transformer) => {
if ((transformer.options.scope & scope) > 0) {
if (Array.isArray(transformedTmpValue) && transformer.options.each) {
Expand Down
10 changes: 5 additions & 5 deletions src/reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
isUnknownProperty,
type PropertyType,
} from './decorators/primitive'
import { EOF, type InstantiableObject } from './types'
import { EOF, ExecutionScope, type InstantiableObject } from './types'
import { useController, type ControllerReader } from './decorators/controller'
import { useTransformer } from './decorators/transformer'
import { useValidators } from './decorators/validator'
Expand Down Expand Up @@ -100,10 +100,10 @@ export function binread<Target> (content: Cursor, ObjectDefinition: Instantiable
return useBitField(bitfields, instance, content)
}

usePrePost(Meta.getClassPre(metadata), instance, content)
usePrePost(Meta.getClassPre(metadata), instance, content, ExecutionScope.OnRead)

Meta.getFields<Target>(metadata).forEach((field) => {
usePrePost(Meta.getPre(metadata, field.propertyName), instance, content)
usePrePost(Meta.getPre(metadata, field.propertyName), instance, content, ExecutionScope.OnRead)

// TODO [Cursor] Pass the field name information to add to the namespace
const finalRelationField = isUnknownProperty(field) ? useConditions(Meta.getConditions(field.metadata, field.propertyName), instance) : field
Expand All @@ -128,10 +128,10 @@ export function binread<Target> (content: Cursor, ObjectDefinition: Instantiable

instance[field.propertyName] = transformedValue
}
usePrePost(Meta.getPost(metadata, field.propertyName), instance, content)
usePrePost(Meta.getPost(metadata, field.propertyName), instance, content, ExecutionScope.OnRead)
})

usePrePost(Meta.getClassPost(metadata), instance, content)
usePrePost(Meta.getClassPost(metadata), instance, content, ExecutionScope.OnRead)

return instance
}
10 changes: 10 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ export enum PrimitiveSymbol {
char,
}

/**
* The execution scope defines in which part of the binary processing (read,
* write or both).
*/
export enum ExecutionScope {
OnRead = 0x01,
OnWrite = 0x02,
OnBoth = 0x03,
}

/**
* isPrimitiveSymbol.
*
Expand Down
14 changes: 9 additions & 5 deletions src/writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import {
isPrimitiveRelation,
type PropertyType,
} from './decorators/primitive'
import { type InstantiableObject } from './types'
import { ExecutionScope, type InstantiableObject } from './types'
import { usePrePost } from './decorators/prepost'
import { useConditions } from './decorators/condition'
import { useTransformer, TransformerExecutionScope } from './decorators/transformer'
import { useTransformer } from './decorators/transformer'
import { writeBitField } from './decorators/bitfield'

/**
Expand Down Expand Up @@ -64,25 +64,29 @@ export function binwrite<Target> (cursor: BinaryWriter, ObjectDefinition: Instan
return cursor
}

usePrePost(Meta.getClassPre(metadata), instance, cursor, ExecutionScope.OnWrite)

Meta.getFields<Target>(metadata).forEach((field) => {
usePrePost(Meta.getPre(metadata, field.propertyName), instance, cursor)
usePrePost(Meta.getPre(metadata, field.propertyName), instance, cursor, ExecutionScope.OnWrite)

const finalRelationField = isUnknownProperty(field) ? useConditions(Meta.getConditions(field.metadata, field.propertyName), instance) : field
if (finalRelationField !== undefined) {
// Condition don't need to be used since the object are already in here.
const transformers = Meta.getTransformers(metadata, field.propertyName)
const reversedTransformers = transformers.slice().reverse()
const transformedValue = useTransformer(reversedTransformers, instance[field.propertyName], instance, TransformerExecutionScope.OnWrite)
const transformedValue = useTransformer(reversedTransformers, instance[field.propertyName], instance, ExecutionScope.OnWrite)
binWrite(finalRelationField, transformedValue)

// TODO Some controller should include instruction on how to normalize the data
// For instance matrix should normalize the data into a single array
// NullString should add back the \0
// targetType sin
}
usePrePost(Meta.getPost(metadata, field.propertyName), instance, cursor)
usePrePost(Meta.getPost(metadata, field.propertyName), instance, cursor, ExecutionScope.OnWrite)
})

usePrePost(Meta.getClassPost(metadata), instance, cursor, ExecutionScope.OnWrite)

return cursor
}

Expand Down
Loading