2
\$\begingroup\$

First let me describe the problem, we want to solve:

  • We have hierarchical directories structure with different types of templates. Each type of templates is located in dedicated folder.
  • Hierarchical structure can be located everywhere, right now we stores it on our local disc, but in the future it can be moved to bitbucket or something.
  • For now we have these types: templates, which have .tf extension are 'trigger' templates and are located in 'trigger' folder. templates, which have .pf extension are 'process' templates and are located in 'process' folder. files, which have .bas or .py extensions are 'component' files and are located in 'component folder'. All folders are located in root folder, let's call it 'Data'.

Format: 'trigger' and 'proccess' templates are quite similar, with some small adjustments. Template itself looks like 'ini' file, but not exactly. As example:

trigger.tf

[Settings]
TriggerName=ActivatorTrg
IconFile=trigger.ico
Component1=File=x1.bas

procces.pf

Settings]
ProcessName=Archiver
IconFile=process.ico
Component1=File=x1.bas|Name=x1
Component2=File=x2.bas|Name=x2
Component3=File=x3.bas|Name=x3

As you can see, trigger can have only one component and processes can have several of them.

'Component' files have source code written in Microsoft Basic or Python. We want to parse these files and extract all parameter names, which used to call two methods: GetConfigParameter and GetParameter. As example:

...
strIntegration = GetConfigParameter("Integration","10")
strTriggerName = GetParameter("-TriggerName")
...

Here, we would like to extract two parameters: Integration with default value 10 and TriggerName with no default value, but with Hidden flag, what '-' prefix is indicated.

Solution:

As soon as templates can be located in bitbucket, local hard drive or something, we introduce ITemplatesContentProvider interface:

public interface ITemplatesContentProvider
{
    IEnumerable<string> Templates { get; }

    string GetTemplateContent(string templatePath);

    string GetComponentContent(string componentPath);
}

Templates collection returns all templates urls (in our case path to the template file on our local disc). GetTemplateContent returns content of .tf or .pf file and GetComponentContent returns content of associated component (Component1=File=x1.bas)

ITemplatesContentProvider was implemented by abstract class TemplatesContentProvider, which is used almost to pass same TemplatesProviderConfiguration to all template content provider classes.

public class TemplatesProviderConfiguration
{
    public string RootTemplatesLocation { get; }

    public string ComponentsLocation { get; }

    public string[] TemplatesSearchPattern { get; }
}

RootTemplatesLocation - in our case 'Data' folder. ComponentsLocation - 'component' folder and TemplatesSearchPattern - extensions '.tf' and 'pf'.

public abstract class TemplatesContentProvider : ITemplatesContentProvider
{

    protected TemplatesProviderConfiguration Configuration { get; }

    protected TemplatesContentProvider(TemplatesProviderConfiguration configuration)
    {
        if (configuration == null || !configuration.IsValid)
        {
            throw new ArgumentException($"{nameof(TemplatesContentProvider)} configuration is not valid!");
        }

        Configuration = configuration;
    }

....
}

...and then we have concrete implementation like FileTemplatesContentProvider

As soon as 'components' can be written in Basic and Python, which used different syntax and correspondingly parameters will be extracted differently, we have ParameterParserFactory, which implements IParameterParserFactory, which return specific parser through the interface IParameterParser.

public class ParameterParserFactory : IParameterParserFactory
{
    public IParameterParser GetParser(string referencedFile)
    {
        return new BasicParameterParser();
    }
}

public interface IParameterParser
{
    IEnumerable<TemplateParameter> Execute(string content);
}

Besides, as soon as templates can be rather .tf or pf, we also have TemplateParsersFactory, which accepts ITemplatesContentProvider and IParameterParserFactory as constructor parameters and construct ITemplateParser (not parameter parser) class dynamically based on templatePath extension:

public class TemplateParsersFactory
{

    public TemplateParsersFactory(
                ITemplatesContentProvider templatesContentProvider,
                IParameterParserFactory parameterParserFactory)
    {
        if (templatesContentProvider == null || parameterParserFactory == null)
        {
            throw new ArgumentNullException(nameof(templatesContentProvider));
        }

        _templatesContentProvider = templatesContentProvider;
        _parameterParserFactory = parameterParserFactory;
    }
    public ITemplateParser GetParserFor(string templatePath) {...}
}

public interface ITemplateParser
{
    Template Parse(string templatePath);
}

Each ITemplateParser sub class has base class TemplateParser, which as well accepts ITemplatesContentProvider and IParameterParserFactory. The concrete implementations of ITemplatesContentProvider and IParameterParserFactory are constructed in the system as singletons.

And then the usage:

var templatesContentProvider = new FileTemplatesContentProvider(
    new TemplatesProviderConfiguration(@"Data", "component", ".tf"));

var parameterParserFactory = new ParameterParserFactory();

var parserFactory = new TemplateParsersFactory(
    templatesContentProvider, parameterParserFactory);
parserFactory.RegisterParserFor(".tf", typeof(TriggerParser));

foreach (var templateInfo in templatesContentProvider.Templates)
{
    var parser = parserFactory.GetParserFor(templateInfo);
    var template = parser.Parse(templateInfo);
}

I feel a bit bad that I need to inject in TemplateParser <- ParameterParserFactory.

But in general any comments or suggestions?

\$\endgroup\$
  • 1
    \$\begingroup\$ If you could improve the formatting of the code it'd be great. \$\endgroup\$ – t3chb0t May 10 '17 at 16:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.