Jump to content

darnocian

Members
  • Content Count

    111
  • Joined

  • Last visited

  • Days Won

    2

Posts posted by darnocian


  1. In my opinion, I'd try fix the OS configuration, or use a 3rd party if the default provided by the OS isn't good enough.

     

    From an application perspective, there should be a level of trust on basic services provided by system, otherwise there would be a lot of app bloat if everyone had to distrust them... When it comes to the APIs we use from the OS or the RTL, the key thing we need to pay attention to is the locale configuration (defined by the OS again), and use the appropriate toUTC/fromUTC type functionality. This is especially important when applying these functions appropriately when obtaining data from 3rd party sources and converting the timestamps appropriately, which would normally be sourced in UTC.

     

    On windows (desktop and server), there is w32tm.exe (windows time service) https://docs.microsoft.com/en-us/windows-server/networking/windows-time-service/windows-time-service-tools-and-settings. On my workstation this was disabled till I enabled it, but the other benefit is that within a larger organisation, the sysadmins could script something to deploy and enable appropriately with whatever the standard is so that everyone was covered consistently.

     

    I have worked on time sensitive apps before where we had 3rd party time agents on the host machine that synchronised the time, and custom libraries that worked with those agents, but this was because there was a very very low tolerance for any time drift at any time because the application was financial in nature working on financial market data in realtime and logging  required more granular timestamp information.

     

    I'd try to stick to RTL functions or standard system calls like what @Wagner Landgraf referenced above, but that is my opinion.

     


  2. I felt like playing with this idea for Delphi - as like writing parsers. 😉 Years ago I did something similar for a document management system in a different language. I've written a small demo illustrating 2 ways of parsing https://github.com/sempare/sempare-delphi-fts-expression

     

    I did this for fun and in a few hours, so it is still very raw. It contains a hand rolled lexer and parser, but hopefully can give you some ideas how to do this too.

     

    In the first (Sempare.FullTextSearch.SimpleExpression.Test.pas) you can see some scenarios:

    procedure TSimpleExprTest.TestSimpleExpr;
    begin
      assert.AreEqual('name = ''conrad''', tparser.ParseStr('name', '"conrad"'));
    end;
    
    procedure TSimpleExprTest.TestWildCard2Expr;
    begin
      assert.AreEqual('(name like ''conrad%'') or (name like ''%pete'')', tparser.ParseStr('name', '"conrad%" "%pete"'));
    end;
    
    procedure TSimpleExprTest.TestWildCardExpr;
    begin
      assert.AreEqual('name like ''conrad%''', tparser.ParseStr('name', '"conrad%"'));
    
    end;
    
    procedure TSimpleExprTest.TestNotExpr;
    begin
      assert.AreEqual('not (name = ''conrad'')', tparser.ParseStr('name', 'not "conrad"'));
    end;
    
    procedure TSimpleExprTest.TestOrExpr;
    begin
      assert.AreEqual('(name = ''abc'') or (name = ''def'')', tparser.ParseStr('name', '"abc" or "def"'));
    end;
    
    procedure TSimpleExprTest.TestPrecidentExpr;
    begin
      assert.AreEqual('(name = ''abc'') and (not ((name = ''321'') or (name = ''def'')))', tparser.ParseStr('name', '''abc'' and  not ''321'' or  ''def'''));
    end;

     

    The second (Sempare.FullTextSearch.Expression.Test.pas) is a bit more general. It illustrates parsing, but implementation at present is fairly basic as just a pretty printer:

    procedure TExprTest.TestNotExpr;
    begin
      assert.Areequal('not (name != ''conrad'')', tparser.ParseStr('not (name!="conrad")'));
    end;
    
    procedure TExprTest.TestOrExpr;
    begin
      assert.Areequal('(name = ''abc'') or (text = ''def'')', tparser.ParseStr('name = ''abc'' or text = ''def'''));
    end;
    
    procedure TExprTest.TestPrecidentExpr;
    begin
      assert.Areequal('((name = ''abc'') and (value = ''321'')) or (text = ''def'')', //
        tparser.ParseStr('name = ''abc'' and value = ''321'' or text = ''def'''));
    end;
    
    procedure TExprTest.TestStartsWithExpr;
    begin
      assert.Areequal('name like ''abc%''', tparser.ParseStr('name starts with "abc"'));
    end;

    above you can see that the parsing identifies the precedence and places brackets appropriately. Also it changed 'starts with' to 'like'...

     

    NOTE: there are 2 different parsers implemented for the above scenarios (but both named TParser - just depends which unit is being referenced)

     

    Hope you find this useful.

    • Like 2

  3. the only way to get around it appears to be by explicitly setting the clipboard content in the current app (programatically or by the user copying something in the app), but switching between apps will result in the problem occurring again. 

     

    I'm not sure if there is a workaround without Apple being involved - I suspect all we can do is inform users if this becomes annoying.


  4. Yeah, I definitely agree with the benefit of the feature... but I would expect it to only happen when actually pasting from the clipboard - not while typing keys... 

     

    I'll try knock something together with XCode to see what a standard input control like a UITextField would do as comparison... maybe there is something in FMX doing something it should have to.

     

    Will keep you posted.


  5. I was just testing an fmx app on ios and noticed that when editing in a tedit, I keep on getting little notifications at the top of the device '<app>pasted from <device>' every time I enter any character.

     

    I expected that notification to only happen when pasting from other apps on the device, not from the current one and not for every single keypress.

     

    Has anyone encountered this before?


  6. @mvanrijnen @Edwin Yip Thanks for the references. 

     

    The aim with the Sempare Template Engine was to have a template engine that was native to Delphi and could be used for web, but also for other kinds of purposes such as code or config generation. At the time I wrote it, dmustache was mainly what was available, but I wanted a template language that was more readable and pascal-like, and from a library perspective providing more flexibility in terms of extension points (custom functions) and the ability to support various sub-template inclusion patterns (content with header/footer, main template referencing content template, etc) and dynamic loading. Due to the fact that looping is a feature, it also has to be safe and offers a timeout if template processing takes too long due to data processing or bad logic. 

     

    It does rely on RTTI, which does mean there is a performance penalty, but I chose functionality/flexibility over performance to start. I have not had problems reported yet, so I suspect performance has been acceptable for users, as it has been for my use cases. 

     

    Documentation wise, it should be fairly well covered on github, but I have been working on a CHM help edition which can be used from the IDE with context sensitive help which may be a bit more friendly for those that don't want to necessarily have to browse to the the github docs. Please email me conrad@sempare.ltd/conrad.vermeulen@gmail.com if you are interested and can notify you when it is available.


    If you have feature requests/comments feel free to raise them on the issue tracker (https://github.com/sempare/sempare-delphi-template-engine/issues). I do check here periodically, but may miss things or reply late.

     

    In the next release, I'm allowing for discovery of variables and functions that are referenced. So if you provide a template editor and are using a dictionary/json to pass variables, the UI can know upfront what the required variables are and perform additional validation if required, prompting the user if anything is missing. Alternatively, it could be a way to identify variables that are required, allowing for optimal querying of data from database before populating the template. This is just a complementary feature to an existing feature where errors can be raised if a referenced variable is not present.

     

    Further, I hope to create some more examples with more popular web frameworks soon, as I think many people find it easier to see how things work from demos.

     

    I realise licensing as GPL may be contentious, but I've gone with the 'free as in speech vs free as in beer' approach. So for a nominal one time fee, you can use it under the commercial license without any restriction.

     

    I'll keep you posted on new developments as they unfold.

    • Like 4

  7. One of the biggest issues with python, not necessarily python4d, is that everything is boxed. Many analytical packages use some packages like numpy/scipy which have some very powerful  underlying C code that provides the boost without needing the boxing and unboxing of values where the python code is just providing very flexible glue.

    import numpy
    
    def test(size):
         x = numpy.random.rand(size) * 10
         return numpy.logical_or(x > 6, x < 2).astype(int) * 20 + 20
    
    test(500000)

    to get the above to work, you will need to install numpy if you don't already have it:

    pip install numpy

    It may be interesting to see if this approach provides any improvements? In the above, I could see there being a cost with the way it still has to evaluate the logical expression with a callback.

     


  8. v1.4.1 is available on GetIt now.

     

    Sorry, that release is not compatible with XE4 (my CI is running the latest Delphi only - something in my dev pipeline to support more generally).

     

    The version in github has an XE4 compatible fix, but will be bundled into the next release.


  9.  Assert.AreEqual('123.457', Template.Eval('<% x := 123.456789 %><% fmt("%6.3f", x) %>'));

    here is a quick fix in Sempare.Template.Functions.pas:

    class function TInternalFuntions.Fmt(const AArgs: TArray<TValue>): string; 
    var
       LArgs: TArray<TVarrec>;
       LIdx: integer; 
    begin
       setlength(LArgs, high(AArgs));   
       for LIdx := 1 to high(AArgs) do     
           LArgs[LIdx - 1] := AArgs[LIdx].AsVarRec;   
       exit(format(AsString(AArgs[0]), LArgs)); 
    end;

    I need to check into why the current helper does something different. Will get a fix into the main release soon. (fix is on dev branch)

     

     


  10. I created a test and the following is runs properly:
     

    Assert.AreEqual('543.21', Template.Eval('<% x:= 543.21 %><% x %>'));  
    
    Assert.AreEqual('5.1234', Template.Eval('<% x:= 5.1234 %><% x %>'));

    floating point numbers should be using dot (.) as illustrated above.

     

    I'm not sure about fmt() offhand. Will investigate. fmt() is just a wrapper around SysUtils.Format(fmt, ...). Might be some issue with TValue to TVarRec conversion, but will double check. Most of the templates I've worked on passed values into the engine via the input record/class, but appreciate this should not be a problem.

     

    BTW. it is better to raise issues on github. https://github.com/sempare/sempare-delphi-template-engine/issues


  11. I did a few tests using WINE a while ago and was quite impressed. I have never got the IDE itself to run on it since I've used it over the years. However,  creating some 'hello world' console, VCL and FMX apps I was pleasantly surprised they worked quite well, but had seen some 'glitches' with FMX under 64bit.


  12. I think subres should be an index into ChildRecords.

     

     Try change:

    1 hour ago, mvanrijnen said:

    '<% for subres in ChildRecords %>SubRes: |<% subres.Description %>|<br><%end%>'+

    to

    '<% for subresIdx in ChildRecords %>SubRes: |<% ChildRecords[subresIdx].Description %>|<br><%end%>'+

     

    I've just renamed the variable as well to make it clearer.

     

    It may seem undesirable, but did it this way as sometimes you want to know where you are in the array.

     

    If you have many variables, you should be able to do:

    <% with ChildRecords[subresIdx] %> <% Description %> <% Vehicle %> <% end %>

    • Like 1

  13. 3 hours ago, Rollo62 said:

    I like the mustache notation {{}} a lot, is it possible to make this compatible (at least a bit), to Mustache ?

    (Naive approach: Maybe its just replacimg "<%"   "%>" by "{{"   "}}"  ) ?

    I tried to provide flexibility in many places. https://github.com/sempare/sempare-delphi-template-engine/blob/master/docs/configuration.md

     

    begin
      var ctx := Template.Context;
      ctx.StartToken := '{{';
      ctx.EndToken := '}}';
      Assert.IsEqual('hello', Template.Eval(ctx, '{{ if true }}hello{{else}}bye{{end}}'));
    end;

    There is a restriction however. Must be 2 characters in length. It will work as long as it doesn't conflict with any of the other tokens (no validation is done on this however, so you just need to check if you do override)

     

    There is also a global override where you can set GDefaultOpenTag and GDefaultCloseTag. These are defined in Sempare.Template.Context.pas. Once set, you don't have to explicitly create a context if you don't need one.

    • Like 1
    • Thanks 2

  14. Hi,

     

    Just a small announcement that a new version (v1.4.0) has been released.

     

    Changes:

    1. NEW: Context.StreamWriterProvider which allows greater flexibility in providing custom overrides to the StreamWriter
    2. NEW: Support for statement start and end tokens allowing for content to be swallowed (useful when statements are multi line)
    3. NEW: Added helper variables and functions for spaces, newlines, tabs, chr() and ord()
    4. FIX: Issue with custom text writer that supported a few options regarding newlines, but had a bug when it came to carriage returns. (not noticeable under html) 
    5. FIX: Fixed a double free bug in a ParseFile helper.
    6.  UPDATE: Documentation updates 

     

    Context.StreamWriterProvider flexibility
     

      var ctx := Template.Context();
      ctx.StreamWriterProvider := function(const AStream: TStream; AContext: ITemplateContext): TStreamWriter
        begin
          result := TStreamWriter.Create(AStream);
       end;

    The above is an example of how the override can be done on a per context basis. There is a GDefaultStreamProvider which is used when the context is initially created which can be used for global initialisation.

     

    Allow swallowing of content between start and end statement tokens

    Say you have a scenario like:

    <% for i := 1 to 5 %>
      
      <% i %>
        
    <% end %>

    What you may notice is that there are many newlines that appear something like:

        1
    
        2
    
        3
    
        4
    
        5

     

    Now consider the following:

    <% for i := 1 to 5 |>
     <% i %>
    <| end %>

    In this scenario, all normal output between |> and <| are ignored, except statements such as explicit print() or variable references will work as normal.

     

    Why? It just means that the template becomes responsible for any indenting and newlines within the end (|>) and start ( <|) tokens.

     

    ---

     

    If you have any feature requests or bug reports, please raise them on https://github.com/sempare/sempare-delphi-template-engine/issues

     

     

     

     

     

     

     

     

    • Like 1

  15. @mvanrijnen Just looking at the bug you highlighted, there is also an alternative workaround by setting the IContext.NewLine = #$A.

    procedure TTestTemplate.TestNewLine;
    type
      TRec = record
        Value: string;
        description: string;
      end;
    var
      r: TRec;
      s: string;
      ctx: ITemplateContext;
    begin
      ctx := Template.Context();
      ctx.newline := #10;
      r.Value := 'a value';
      r.description := 'some desc';
      s := Template.Eval(ctx, 'Value: <% value %>'#$D#$A'Description: <% description %>', r);
      Assert.AreEqual('Value: a value'#$D#$A'Description: some desc', s);
    end;

    I have a custom StreamWriter that allows for stripping repeated whitespace and new lines. I'll look into also providing a way to override that better.

×