DustyGhost.NGenerator 1.0.2

dotnet add package DustyGhost.NGenerator --version 1.0.2
                    
NuGet\Install-Package DustyGhost.NGenerator -Version 1.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DustyGhost.NGenerator" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DustyGhost.NGenerator" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="DustyGhost.NGenerator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DustyGhost.NGenerator --version 1.0.2
                    
#r "nuget: DustyGhost.NGenerator, 1.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DustyGhost.NGenerator@1.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DustyGhost.NGenerator&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=DustyGhost.NGenerator&version=1.0.2
                    
Install as a Cake Tool

NGenerator (Narrative Generator)

NGenerator (the N stands for Narrative) is a template and tag substitution system. Supply the code some rules in the form of either Json or the InputHolder model, and it will convert the input into a random set of strings. This is usful for building all sorts of procedurally generated narratives.

GitHub - dustyghost/NGenerator

Nuget -DustyGhost.NGenerator

Code setup

Setup is really easy, just import the NGenerator.Processors namespace.

When you instantiate NGenerratorProcessor.

You don't have to supply a Random() instance to the consructor, as the code will create one internally if not supplied. But if you want to control the seed, the example below shows how.

Then you just call the Process(string input) method, passing your Json string.

using NGenerator.Processors;
       
 public void Generate()
{
    var rnd = new Random(1);

    var nGeneratorProcessor = new NGenerratorProcessor(rnd);
    string input = "{\"Template\":\"Hello {{sub}}!\",\"Tags\":{\"sub\":[\"World\",\"You\"]}}";

    var result = nGeneratorProcessor.Process(input);
    Console.Write(result); //Hello World!
}

If you wish, instead of using Json, you can pass an instance of NGenerator.Models.InputHolder model to the Processor.

using NGenerator.Processors;
       
public void UseInputHolder()
{
    var rnd = new Random(1);
    var nGeneratorProcessor = new NGenerratorProcessor(rnd);

    InputHolder inputHolder = new InputHolder
    {
        Template = "Hello {{sub}}!",
        Tags = new Dictionary<string, string[]>
        {
            { "sub", new string[] {"World", "You"} }
        }
    };

    var result = nGeneratorProcessor.Process(inputHolder);

    Console.Write(result); //Hello World!
}

Basic Inputs

This is made up of the Template and Tags.

In the example below, the {{sub}} will be replaced with one of the strings in the Tags.sub array.

So the results of this could be Hello World! or Hello You!

For example:

{
  "Template": "Hello {{sub}}!",
  "Tags": {
    "sub": [
      "World",
      "You"
    ]
  }
}

Tags in Tags

You can even have tags in tags. These will be replaced recursively, allowing for very powerful string generation.

{
  "Template": "My Name is {{name}}!",
  "Tags": {
    "surname": [
      "Johnson",
      "Smith"
    ],
    "name": [
      "John {{surname}}"
    ]
  }
}

So in the previous example, this would end up either John Smith or John Johnson

Pluralization

You can tell the generator to pluralize a word by using the .p tag modifier

{
  "Template": "{{name}} really {{feels.p}} oranges!",
  "Tags": {
    "surname": [
      "Smith"
    ],
    "name": [
      "John {{surname}}"
    ],
    "feels": [
      "love",
      "hate"
    ]
  }
}

In this example, love becomes loves, hate becomes hates for example: John Smith really loves oranges!

Plural Definitions

You can also force the generator to use certain words for plurals, using the ::p. definition. For example, the plural of fish is "fish".

{
  "Template": "John really likes {{thing.p}}!",
  "Tags": {
    "thing": [
      "fish:p.fish"
    ]
  }
}

This will produce John really likes fish!, not fishes or fishs 😃

Upper and Lower Case

There are a few modifiers you can use to change the case of a word.

  • u = uppercase first letter of word
  • U = uppercase whole word
  • l = lowercase whole word

for example, in the following json, john will be selected and become John:

{
  "Template": "{{name.u}} really {{feels.p}} oranges!",
  "Tags": {
    "surname": [
      "smith"
    ],
    "name": [
      "john {{surname.u}}"
    ],
    "feels": [
      "love",
      "hate"
    ]
  }
}

Add 'a' or 'an' Prefix

You can prefix a word with a or an using the .a modifier. This is limited to putting an in front of a, e, i, o and u. And will put a in front of the rest.

This is obviously not ideal for words like unicorn which is a unicorn

So you can use the ::a. definition.

For example:

{
  "Template": "John really wants {{thing.a}}!",
  "Tags": {
    "thing": [
      "unicorn:a.a"
    ]
  }
}

This will produce John really wants a unicorn!

So if it was "unicorn:a.an" then the result would have been John really wants an unicorn!. Obviously this is incorrect, but shows how this functionality works.

Reserve Substitutions

So as an example, we want to ensure that once a substituted tag happens, we use the same word for the next instance of that tag. In this case we can use the number modifier. For example .1

{
  "Template": "{{sentance}}",
  "Tags": {
    "sentance": [
      "{{name.u.1}} was happy with {{thing.1.p}}, {{name.u.1}} was kind to one {{thing.1}}. The {{thing.1}} was always kind to {{name.u.2}}!"
    ],
    "name": [
      "john",
      "pete",
      "alph"
    ],
    "feels": [
      "happy"
    ],
    "thing": [
      "puppy",
      "cat"
    ]
  }
}

So in the previous example, if {{name.1}} returns john, the next time {{name.1}} is found then john would be used.

Note, other modifiers are applied after the reserve. So {{name.1}} which would make john, if {{name.u.1}} was the next to be encountered then John would be returned (uppercase first letter, but same word).

Also, in the previous example, if John was selected to be reserved for {{name.1}} and the next tag was {{name.2}}, the process will select from the remaining unreserved names, so pete or alph would be selected. If there are no more unreserved names available, then the process will just pick a random one from all the names, reserved or not.

Credit where it is due

This code was inspired by, but not a direct port of Tracery. There are differences in the way each of these function, but I felt it only right to attribute the inspiration for this project to GalaxyKate

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.1 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.2 1,632 7/15/2021
1.0.1 441 6/30/2021
1.0.0 455 6/30/2021