# GirGen

GIR Parser and type generator.

## Install

There are pre built binaries for `linux-x64` and `linux-arm64` published on NPM.

```sh
npm install girgen -D
./node_modules/.bin/girgen --help
```

Otherwise you can install from crates.io using `cargo`.

```sh
cargo install girgen --root .
./bin/girgen --help
```

## TypeScript

Generate a standalone package that contains every namespace found in the given
directories.

```sh
girgen typescript --help
```

> [!TIP]
>
> You can use the Gnome flatpak SDK to acquire GIR files on systems that don't
> have them in one place, e.g NixOS or when you are targeting Flatpak.
>
> ```sh
> flatpak run --command=cp --filesystem=home org.gnome.Sdk -r /usr/share/gir-1.0 gir-1.0
> girgen -d gir-1.0 typescript
> ```

By default it will generate the package to `.types/gi` which you can then source
in `tsconfig.json`.

```json
{
  "compilerOptions": {
    "lib": ["es2024"], // don't forget to specify a `lib` to avoid sourcing TypeScript's `dom` lib
    "skipLibCheck": true, // it's recommended to turn this on
    "typeRoots": [".types"]
  }
}
```

> [!TIP]
>
> Don't forget to gitignore generated files.
>
> ```sh
> echo ".types/gi/" > .gitignore
> ```

Note that when using `--alias` flag to generate non version imports such as
`gi://Gtk` make sure to ignore the version you don't need so that it does not
end up as a union of the two versions.

```sh
girgen typescript -i Gtk-3.0 --alias
```

### TypeScript Annotations

GObject has a few additional concepts about class methods and properties that
cannot be expressed with TypeScript alone. For these girgen generates type only
fields on classes and interfaces.

We have annotations for:

- signals
- readable properties
- writable properties
- construct-only properties

When implementing a GObject subclass you might want to annotate a subset of
these or all of these depending on usecase.

Every class that inherits from GObject is going to include a namespace
containing type declarations where each member is written in `kebab-case`:

```ts
namespace MyClass {
  export interface SignalSignatures extends GObject.Object.SignalSignatures {
    // simple signal
    "my-signal"(arg: number): void
    // detailed signals are annotated with the `::{}` suffix
    "my-detailed-signal::{}"(arg: number): void
  }

  // ReadableProperties is also used for notify signal annotations
  export interface ReadableProperties
    extends GObject.Object.ReadableProperties {
    // property which has a public getter
    "my-prop": number
  }

  export interface WritableProperties
    extends GObject.Object.WritableProperties {
    // property which has a public setter
    "my-prop": number
  }

  export interface ConstructOnlyProperties
    extends GObject.Object.ConstructOnlyProperties {
    // property which can only be set at construction
    "my-ctor-prop": number
  }
}
```

And the Class will refer to these using special `$` prefixed fields:

> [!IMPORTANT]
>
> These fields don't exist at runtime, they are used by other APIs to introspect
> GObjects.

```ts
class MyClass extends GObject.Object {
  declare readonly $signals: MyClass.SignalSignatures
  declare readonly $readableProperties: MyClass.ReadableProperties
  declare readonly $writableProperties: MyClass.WritableProperties
  declare readonly $constructOnlyProperties: MyClass.ConstructOnlyProperties

  static {
    GObject.registerClass(
      {
        Signals: {
          "my-signal": {
            param_types: [GObject.TYPE_DOUBLE],
          },
          "my-detailed-signal": {
            param_types: [GObject.TYPE_DOUBLE],
            flags: GObject.SignalFlags.DETAILED,
          },
        },
        Properties: {
          "my-prop": GObject.ParamSpec.double(
            "my-prop",
            null,
            null,
            GObject.ParamFlags.READWRITE,
            -GObject.Double.MAX_VALUE,
            GObject.Double.MAX_VALUE,
          ),
          "my-ctor-prop": GObject.ParamSpec.double(
            "my-ctor-prop",
            null,
            null,
            GObject.ParamFlags.CONSTRUCT_ONLY,
            -GObject.Double.MAX_VALUE,
            GObject.Double.MAX_VALUE,
          ),
        },
      },
      MyClass,
    )
  }

  // GObject.ConstructorProps can be used to infer props from the annotations
  constructor(props: Partial<GObject.ConstructorProps<MyClass>>) {
    super(props)

    // note that properties will be annotated as camelCase
    console.log(props.myProp, props.myCtorProp)
  }
}
```

Methods such as `connect()`, `emit()`, `notify()` will infer from these
annotations.

```ts
const instance = new MyClass()

instance.connect("my-signal", (source, arg) => {
  console.log(arg)
})

instance.connect("my-detailed-signal::detail", (source, arg) => {
  console.log(arg)
})

instance.connect("notify::my-prop", (_, pspec) => {
  console.log(pspec.name)
})
```

Due to how TypeScript `this` type works, you need to annotate `this` or use a
typecast to correctly infer types within the class.

```ts
class MyClass {
  myFn(this: MyClass) {
    this.emit("my-signal", 0)
  }

  myFn() {
    const self = this as MyClass
    self.emit("my-signal", 0)
  }
}
```

### Module Augmentation

If you are using
[`Gio._promisify`](https://gjs.guide/guides/gjs/asynchronous-programming.html#promisify-helper)
you can augment namespaces.

```ts
import Gio from "gi://Gio?version=2.0"
import GLib from "gi://GLib?version=2.0"

Gio._promisify(
  Gio.InputStream.prototype,
  "read_bytes_async",
  "read_bytes_finish",
)

declare module "gi://Gio?version=2.0" {
  namespace GI {
    namespace Gio {
      interface InputStream {
        read_bytes_async(
          count: number,
          io_priority: number,
          cancellable: Gio.Cancellable | null,
        ): GLib.Bytes
      }
    }
  }
}

declare const stream: Gio.InputStream
const bytes = await stream.read_bytes_async(4096, GLib.PRIORITY_DEFAULT, null)
```
