tmark

Pure Dart types, parser, and serializer for tmark. Works without Flutter, reflection, or runtime code generation.

Installation

This package is being prepared for its first pub.dev release. For now, use a local path dependency. After publication:

dart pub add tmark

Parse and serialize

import 'package:tmark/tmark.dart';

final paragraph = unmarshal('{p;Hello world}');
final source = marshal(paragraph); // {p;Hello world}

final document = Document(
  title: 'Hello',
  content: [Paragraph([TText('Hello world')])],
);
final wire = marshal(document);
final parsed = unmarshalDocument(wire);

unmarshal accepts a single value. unmarshalDocument additionally requires a Document. Invalid syntax or schema values throw FormatException.

Use soft: true to preserve unknown tags as Unknown(raw) instead of rejecting them. Known nodes still undergo validation. Serialization preserves unknown raw nodes and escapes text; it does not preserve every original spelling of known nodes. Aggregate nesting is limited to maxDepth (16).

Built-in models cover all types from tmark compatibility layer 1.

For Flutter rendering, use tmark_flutter.

Custom types

Applications can register handwritten schemas for their own node types. For example, an inline mention node:

import 'package:tmark/tmark.dart';

final class Mention extends TmarkValue implements RichNode {
  const Mention({required this.user, this.children = const []});

  final String user;
  final RichText children;

  static final schema = () {
    final user = TmarkField<Mention, String>(
      'user', FieldTypes.string, (value) => value.user,
    );
    final children = TmarkField<Mention, RichText>(
      null, FieldTypes.richText, (value) => value.children,
      defaultValue: () => const [],
    );
    return NodeType<Mention>(
      'mention',
      fields: [user, children],
      create: (fields) => Mention(
        user: fields.get(user),
        children: fields.get(children),
      ),
    );
  }();
}

final codec = TmarkCodec(
  registry: TmarkRegistry.standard.registering([Mention.schema]),
);
final value = codec.unmarshal('{mention;#user{alice}Alice}');
final source = codec.marshal(value);

Registries are immutable and reject duplicate tags or model types. TmarkRegistry.empty starts without built-ins; global functions use the standard registry. For more complex schemas, use RecordType, FieldTypes.scalar, FieldTypes.record, and FieldType.mapped.

The repository's internal generator only rebuilds the checked-in schemas of built-in models.

Development

dart pub get
dart run tool/generate.dart
dart analyze
dart test

Generated files are included in the package.

Libraries

tmark