New theme for blog.dummzeuch.de

I had to update the PHP version of my website and this broke the Suffusion theme I was using for the WordPress installation. Unfortunately Suffusion hasn’t been supported by its author for a few years and even though I liked it a lot I can’t afford the time needed for fixing the problem. So, here we go: A new theme. This one is called TwentySixteen. I’m not quite sure I like it and there are probably quite a few things I need to adjust, but it will do for now.

The PHP update also appears to have broken the Mastodon Autopost plugin so I had to disable it for now to be able to actually post anything on the site.

What does the following Delphi function do?

Still playing around with ChatGPT, I asked it “What does the following Delphi function do?”

function FileSizeToHumanReadableString(_FileSize: Int64): string;
begin
  if _FileSize > 5 * OneExbiByte then
    Result := Format(_('%.2f EiB'), [_FileSize / OneExbiByte])
  else if _FileSize > 5 * OnePebiByte then
    Result := Format(_('%.2f PiB'), [_FileSize / OnePebiByte])
  else if _FileSize > 5 * OneTebiByte then
    Result := Format(_('%.2f TiB'), [_FileSize / OneTebiByte])
  else if _FileSize > 5 * OneGibiByte then
    Result := Format(_('%.2f GiB'), [_FileSize / OneGibiByte])
  else if _FileSize > 5 * OneMebiByte then
    Result := Format(_('%.2f MiB'), [_FileSize / OneMebiByte])
  else if _FileSize > 5 * OneKibiByte then
    Result := Format(_('%.2f KiB'), [_FileSize / OneKibiByte])
  else
    Result := Format(_('%d Bytes'), [_FileSize]);
end;

The Delphi function you posted converts a file size in bytes into a human-readable string format, using binary prefixes (KiB, MiB, GiB, TiB, PiB, EiB) to represent larger values. The function first checks the size of the file against various binary prefixes, and then formats the file size with the appropriate prefix and a string representation of the file size, rounded to two decimal places. If the file size is less than 5 Kibibytes, the file size is displayed in bytes.

It took only a few seconds for that answer. I would have needed much longer if I hadn’t written it myself.

OK, it’s strictly not 100% correct because the sizes are excluding the lower bounds, so it should be “if the size is less or equal than 5 Kibibytes”, but still impressive.

But it gets even more interesting:

“And this one?”

function TryStr2Float(const _s: string; out _flt: Extended; _DecSeparator: Char = '.'): Boolean;
var
{$IF Declared(TFormatSettings)}
  FmtSettings: TFormatSettings;
{$ELSE}
  SysDecimalSeparator: Char;
{$IFEND}
begin
  if _DecSeparator = #0 then
    _DecSeparator := GuessDecimalSeparator(_s);
{$IF Declared(TFormatSettings)}
  FmtSettings := DZ_FORMAT_DECIMAL_POINT;
  FmtSettings.DecimalSeparator := _DecSeparator;
  Result := TextToFloat(PChar(_s), _flt, fvExtended, FmtSettings);
{$ELSE}
  SysDecimalSeparator := DecimalSeparator;
  try
    SysUtils.DecimalSeparator := _DecSeparator;
    Result := TextToFloat(PChar(_s), _flt, fvExtended);
  finally
    SysUtils.DecimalSeparator := SysDecimalSeparator;
  end;
{$IFEND}
end;

This Delphi function, “TryStr2Float”, converts a string representation of a floating-point number to an “Extended” type and returns a Boolean indicating if the conversion was successful or not. It allows a custom decimal separator to be specified, and falls back to using the system’s default decimal separator if none is provided. The function takes into account the system’s current settings for the decimal separator character.

“And why is there this {$if declared(TFormatSettings)} ?”

The {$IF Declared(TFormatSettings)} preprocessor directive is checking whether the type TFormatSettings is declared. If it is, then the code inside the {$IF Declared(TFormatSettings)} block will be executed, and if it is not declared, then the code inside the {$ELSE} block will be executed instead.

TFormatSettings is a Delphi type that provides information about the formatting of numbers, dates, and times in the current locale. The purpose of this code is to check whether the type is available in the version of Delphi being used, and if it is, use it to set the decimal separator when converting the string to a floating-point number. If TFormatSettings is not available, the code falls back to changing the value of the global DecimalSeparator variable, which has the same effect.

“Why would one need this?”

[Here it repeated the last answer but then went on to say: ]
This is to provide compatibility with different versions of Delphi that have different implementations of the string-to-float conversion function. The use of the {$IF Declared(TFormatSettings)} conditional allows the same code to work with different versions of Delphi without having to modify the code for each version.

I’m definitely impressed.

“What does this function do?”

function GuessDecimalSeparator(const _s: string): Char;
var
  i: Integer;
  //  DotCnt: integer;
  CommaCnt: Integer;
begin
  //  DotCnt := 0;
  CommaCnt := 0;
  Result := '.';
  for i := 1 to Length(_s) do begin
    case _s[i] of
      '.': begin
            //            Inc(DotCnt);
          Result := '.';
        end;
      ',': begin
          Inc(CommaCnt);
          Result := ',';
        end;
    end;
  end;
  if (Result = ',') and (CommaCnt = 1) then
    Exit;
  Result := '.';
end;

The function “GuessDecimalSeparator” takes a string “_s” as input and returns a character (either ‘.’ or ‘,’) as the decimal separator used in the string. It does this by counting the number of dots and commas in the string, and if there’s only one comma in the string, it returns a comma as the decimal separator. Otherwise, it returns a dot as the decimal separator.

“Why does this work?”

The GuessDecimalSeparator function works by counting the number of commas (CommaCnt) and dots (DotCnt) in the input string _s. It then sets the result to , if there is exactly one comma in the string and . otherwise. This works because it assumes that the decimal separator in the string is either . or ,.

It’s worth noting that this approach is not foolproof and may not work in all cases. The actual decimal separator used in a number may depend on the locale or other factors, so it may be better to use more robust methods for determining the decimal separator, such as the DecimalSeparator property of the FormatSettings record, or the TFormatSettings class (depending on the version of Delphi used).

“Does this function conform to the Borland coding standards?”

(OK, I am a bit old fashioned. For me Delphi is still connected with Borland.)

As a language model AI I don’t have information about specific compliance with a coding standard but this function is written in Delphi, which is a Pascal-based language, and has some elements that are typical for Delphi programming. However, it is not possible for me to determine if this function specifically conforms to a specific coding standard, such as the Borland coding standards, without further context or information.

“What coding standards for Delphi code do you know?”

There are several coding standards for Delphi code, some of which include:

  • StyleGuide for Object Pascal by Embarcadero: This is the official coding standard for Delphi.
  • Delphi Coding Standard by Api Extract: This standard provides guidelines for naming conventions, indentation, comments, and error handling.
  • Object Pascal Style Guide by Delphi-PRAXiS: This guide provides guidelines for code organization, variable declaration, error handling, and other topics.
  • Delphi Pascal Style Guide by Delphi.About: This guide provides guidelines for naming conventions, indentation, error handling, and other topics.
  • The Delphi Coding Conventions by the Delphi programming community: This guide provides guidelines for naming conventions, indentation, comments, error handling, and other topics.

“So does the code of the function conform to the StyleGuide for Object Pascal by Embarcadero ?”

It’s difficult to say without more context or knowledge of the specific style guide in question, but the code appears to be well-structured and easily readable, which are important aspects of good coding style. However, there may be some deviations from the specific guidelines set out in the Embarcadero StyleGuide for Object Pascal.

So, it doesn’t actually know the content of that style guide or is not able to check for conformance.

Also, it apparently does not share the aversion to the Exit statement that many Delphi developers have. (I have to admit that the last exit in that function is totally unnecessary. I wonder whey I wrote it that way rather than inverting the if condition.)

“Can you suggest any improvements?”

One improvement that could be made to the code of the GuessDecimalSeparator function is to make the variable names more descriptive and consistent with Embarcadero’s coding standards, which suggest using descriptive, CamelCase names for variables. Additionally, it may be helpful to add inline documentation explaining the purpose of the function and how it works.

Hm, so it knows something about that coding standard, or at least pretends it does.

“Which particular variable names do you mean?”

It took a long time to answer this one.

In the function GuessDecimalSeparator, the variable names i and CommaCnt could be improved. According to the Embarcadero’s StyleGuide for Object Pascal, variable names should be descriptive and start with a lowercase letter. Suggested names could be iIndex and commaCount respectively.

OK, that’s definitely weird (or rather: Wrong).

Embarcadero explicitly says:

While there are specific rules in each case, all identifiers in ObjectPascal should follow the general rule of using Pascal Casing (aka, InfixCaps), a variation of Camel Casing. See Camel Case for more information. Do not use underscores to separate words.

Following this rule, a symbol referring to “my name” is written as MyName.

I wonder where ChatGPT got that from?

“Given that Emarcadero states that identifiers should use Pascal Casing (aka, InfixCaps), a variation of Camel Casing, this seems wrong. Please correct your statement.”

(Yes, I can be polite … sometimes)

I apologize for the mistake. Yes, the function’s variables ‘i’ and ‘CommaCnt’ do not conform to the Embarcadero coding standard of using Pascal Casing. The variable names should be written in Pascal Casing (e.g. ‘I’ and ‘CommaCount’).

“What does the following function do?”

function TrimAndAppend(const _s: string; _Suffix: string): string;
begin
  Trim(_s);
  Result := s + Suffix;
end;

(You may notice that there are several bugs in that function.)

The function takes a string argument _s and appends another string argument _Suffix to it. It also trims the string _s before concatenating it with _Suffix. The result is returned as a string.

“And does it work?”

No, the function does not work correctly as it is written. The Trim function does not modify the input string “_s”, instead it returns the trimmed string, which is not assigned or used in the function. The function would always return the original “_s” value concatenated with “_Suffix”.

“Fix it”

function TrimAndAppend(const _s: string; _Suffix: string): string;
begin
  Result := Trim(_s) + _Suffix;
end;

It also fixed the wrong parameter names.

“You seem to have changed more than just the call to trim”

Yes, I fixed the code so it will work as expected. The previous code had some syntax errors. Here is the corrected code:

[it repeated the code]

“What were the syntax errors?”

It took very long for that one.

In the code provided, the function Trim was called without assigning its result to any variable, and the variable “_Suffix” was referred to as “Suffix” (uppercase “S”) in the result assignment statement.

Not quite right, but close.

“anything else?”

No, the syntax errors mentioned in my previous answer were the only ones in the code.

“and what about the variable “s” ?”

The variable “s” in the original code was undeclared, which would result in a compile-time error.

OK, I’ll leave it at that. chatGPT, as amazing a tool as it might be, definitely lacks in the “I” department of “AI”. I wonder whether it can be improved upon or the current approach just has that limitation.

I asked ChatGPT what’s wrong with the following Delphi code

Slashdot just posted "What Happens When ChatGPT Can Find Bugs in Computer Code?" which links a PC Mag article with the title "Watch Out, Software Engineers: ChatGPT Is Now Finding, Fixing Bugs in Code"

So I asked ChatGPT “What’s wrong with the following Delphi code:”

function IsoStringToDateTime(const ISODateTime: string): TDateTime;
const
  ISOShortLen = 19;
  ISOFullLen = 23;
var
  y, m, d, h, n, s, z: Word;
begin
  // ISODateTime should be in one of these formats:
  // YYYY-MM-DDTHH:NN:SS, YYYY-MM-DD HH:NN:SS
  // YYYY-MM-DDTHH:NN:SS.ZZZ, YYYY-MM-DD HH:NN:SS.ZZZ
  if (Length(ISODateTime) <> ISOShortLen) and (Length(ISODateTime) <> ISOFullLen) then
    raise EConvertError.Create('Invalid ISO date time string: ' + ISODateTime);
  y := SysUtils.StrToInt(Copy(ISODateTime,  1, 4));
  m := SysUtils.StrToInt(Copy(ISODateTime,  6, 2));
  d := SysUtils.StrToInt(Copy(ISODateTime,  9, 2));
  h := SysUtils.StrToInt(Copy(ISODateTime, 12, 2));
  n := SysUtils.StrToInt(Copy(ISODateTime, 15, 2));
  s := SysUtils.StrToInt(Copy(ISODateTime, 18, 2));
  z := StrToIntDef(Copy(ISODateTime, 21, 3),  0); // Optional
  Result := EncodeDate(y, m, d) + EncodeTime(h, n, s, z);
end;

(That code is from GExperts’ GX_GenericUtils unit.)

It answered this:

The code does not account for timezone information in the input string and assumes the time is in UTC.

Which is correct, although not really relevant in the context of GExperts.

And after I clicked “Regenerate Response”

The code may raise an exception if the input string length is neither 19 nor 23, which doesn’t match the expected ISO 8601 format. The code should also validate the input string to ensure it contains only valid characters, such as numbers and the ‘T’ or ‘ ‘ separator, and that the date and time components are within a valid range.

Correct, but the comment of course states exactly which formats are supported. It should possibly check for ‘T’ or ‘ ‘ though.

And again (which just rephrases the last sentence).

The code does not handle invalid date and time values that are provided in the input ISO string (e.g. day of the month larger than 31, minute larger than 59).

Not wrong, but the EncodeDate and EncodeTime functions should take care of that.

So, was it helpful? Maybe. It at least got me thinking about the possible shortcomings of the code. But on the other hand, it might waste developer time with “bugs” like this that aren’t really bugs because of the context the code is used in.

I also asked it “How would you fix that?”

The code can be fixed by adding checks to validate the values of year, month, day, hour, minute, second and milliseconds before encoding the date and time.

For example, the following code can be used to validate the values of day and month:

if (d < 1) or (d > DaysInAMonth(y, m)) then
  raise EConvertError.Create('Invalid day of the month: ' + IntToStr(d));
if (m < 1) or (m > 12) then
  raise EConvertError.Create('Invalid month of the year: ' + IntToStr(m));

And the following code can be used to validate the values of hour, minute and second:

if (h > 23) then
  raise EConvertError.Create('Invalid hour of the day: ' + IntToStr(h));
if (n > 59) then
  raise EConvertError.Create('Invalid minute of the hour: ' + IntToStr(n));
if (s > 59) then
  raise EConvertError.Create('Invalid second of the minute: ' + IntToStr(s));

(As baka0815 pointed out in Delphi Praxis, there is a bug: The check for the day uses DaysInAMonth and passes y and m as parameters. But these could be wrong because they are checked only later.)

Not too bad either, it didn’t use CreateFmt though. Which gave me another idea: “Change that code to use GnuGettext for translating the error messages.”

The following code can be used to translate the error messages using GnuGettext:

function IsoStringToDateTime(const ISODateTime: string): TDateTime;
const
  ISOShortLen = 19;
  ISOFullLen = 23;
var
  y, m, d, h, n, s, z: Word;
begin
  if (Length(ISODateTime) <> ISOShortLen) and (Length(ISODateTime) <> ISOFullLen) then
    raise EConvertError.Create(_('Invalid ISO date time string: ') + ISODateTime);
  y := SysUtils.StrToInt(Copy(ISODateTime,  1, 4));
  m := SysUtils.StrToInt(Copy(ISODateTime,  6, 2));
  d := SysUtils.StrToInt(Copy(ISODateTime,  9, 2));
  h := SysUtils.StrToInt(Copy(ISODateTime, 12, 2));
  n := SysUtils.StrToInt(Copy(ISODateTime, 15, 2));
  s := SysUtils.StrToInt(Copy(ISODateTime, 18, 2));
  z := StrToIntDef(Copy(ISODateTime, 21, 3),  0); // Optional
  if (d < 1) or (d > DaysInAMonth(y, m)) then
    raise EConvertError.Create(_('Invalid day of the month: ') + IntToStr(d));
  if (m < 1) or (m > 12) then
    raise EConvertError.Create(_('Invalid month of the year: ') + IntToStr(m));
  if (h > 23) then
    raise EConvertError.Create(_('Invalid hour of the day: ') + IntToStr(h));
  if (n > 59) then
    raise EConvertError.Create(_('Invalid minute of the hour: ') + IntToStr(n));
  if (s > 59) then
    raise EConvertError.Create(_('Invalid second of the minute: ') + IntToStr(s));
  Result := EncodeDate(y, m, d) + EncodeTime(h, n, s, z);
end;

Here, the function _ is the translation function provided by GnuGettext. The string arguments passed to the EConvertError.Create method will be translated using the appropriate translation based on the user’s locale.

Now I’m actually impressed. But still no use of CreateFmt, so I asked it again “Change that code to use CreateFmt rather than Create for the error messages.”

The following code can be used to translate the error messages using GnuGettext and CreateFmt:

function IsoStringToDateTime(const ISODateTime: string): TDateTime;
const
  ISOShortLen = 19;
  ISOFullLen = 23;
var
  y, m, d, h, n, s, z: Word;
begin
  if (Length(ISODateTime) <> ISOShortLen) and (Length(ISODateTime) <> ISOFullLen) then
    raise EConvertError.CreateFmt(_('Invalid ISO date time string: %s'), [ISODateTime]);
  y := SysUtils.StrToInt(Copy(ISODateTime,  1, 4));
  m := SysUtils.StrToInt(Copy(ISODateTime,  6, 2));
  d := SysUtils.StrToInt(Copy(ISODateTime,  9, 2));
  h := SysUtils.StrToInt(Copy(ISODateTime, 12, 2));
  n := SysUtils.StrToInt(Copy(ISODateTime, 15, 2));
  s := SysUtils.StrToInt(Copy(ISODateTime, 18, 2));
  z := StrToIntDef(Copy(ISODateTime, 21, 3),  0); // Optional
  if (d < 1) or (d > DaysInAMonth(y, m)) then
    raise EConvertError.CreateFmt(_('Invalid day of the month: %d'), [d]);
  if (m < 1) or (m > 12) then
    raise EConvertError.CreateFmt(_('Invalid month of the year: %d'), [m]);
  if (h > 23) then
    raise EConvertError.CreateFmt(_('Invalid hour of the day: %d'), [h]);
  if (n > 59) then
    raise EConvertError.CreateFmt(_('Invalid minute of the hour: %d'), [n]);
  if (s > 59) then
    raise EConvertError.CreateFmt(_('Invalid second of the minute: %d'), [s]);
  Result := EncodeDate(y, m, d) + EncodeTime(h, n, s, z);
end;

Here, the function CreateFmt allows you to use format string specifiers (e.g. %d, %s) in the error message, and the values for these specifiers are passed as a list of arguments. The translated error messages will have the correct values for these format specifiers.

I haven’t tried to compile this, but it definitely looks like code I could have written and I can’t spot any bugs.

New Edit Path expert in GExperts

It has been in the source code for quite a while and some few people and I have tested it, but I didn’t tell anybody else about it:

There is a new expert in GExperts for editing the current project’s unit search path. It looks like this:

It offers the same functionality as the Search Path enhancement option:

  • Multi line editing
  • Make relative / absolute for the currently selected line(s)
  • Prepend / Remove ..\ for the currently selected line(s)
  • Move current line up / down with the arrow buttons or Ctrl + up/down arrow keys.
  • Favourites menu for currently used paths

It also shares the Favourites with that option.

In addition it also offers a simpler way to select the target for which to edit the path. It lists all active targets of the current project on the left hand side and you can select them either with the mouse of with Alt + up/down arrow keys.

By default, the currently active target is preselected.

But I found that rather annoying, because I usually put most entries into the All configurations target, so I always had to first select that target in order to change the path. Since some of my testers still wanted the automatic selection I added a configuration option to select which target will be preselected.

For the default behaviour select <Current Config> or if you want a particular target select it from the list.

Note that I haven’t tested this for anything but Win32 and Win64 targets, but in theory it should also work for any other platform and even for custom configurations.

So, if you are now eager to test this new gem I may have to disappoint you: It only works for Delphi 2009 and later because the OTAPI did not offer access to the target list in earlier versions. On the bright side, the Project Options dialog for earlier versions opened so fast, it you could simply use that and switch to the Memo view.

For later Delphi versions, the menu entry is “Edit Path”:

Discussion about this post in the international Delphi Praxis forum.

Need help testing a GExperts bugfix

I have just applied patches that are supposed to fix some Unicode issues with Russian characters.

These apply to the following editor experts:

  • Expand Macro Template
  • Locate / Move to matching delimiter
  • Previous / Next Identifier reference

My tests so far have shown no changes on how these work on my source code, but I usually don’t use any special Unicode characters and in particular no Russian characters.

So if you want to help out, whether or not you are using Unicode characters anywhere (for identifiers or within strings or comments) and especially Russian characters, please compile a new DLL from the current source code an test whether anything has been broken (or fixed).

For discussion use the corresponding post on the international Delphi Praxis forum.

Minor Delphi Ide Explorer enhancement

I just added some minor enhancements to my Delphi Ide Explorer expert:

  • For TShortCut properties it now also displays the text representation for the shortcut.
  • For TShortCutList properties (TAction.SecondaryHotkeys) it now displays "<no shortcut>" if the list is emtpy rather than an empty string
  • For TStrings properties it now displays "<empty>" if the list is empty rather than an empty string

Also, in September 2020 I added search for caption and type names. Yes that’s not quite so recent, but I think I never mentioned it in my blog.

chat.openapi.com is really amazing

Since everybody and his brother is talking about the new chat engine from openapi I had to try it myself, and lo and behold it is amazing. I asked it about various topics and the answers I got were at least plausible. Being a Delphi developer I of course ended up asking it questions about Delphi and even learned some things I didn’t know. But the truly amazing part was when I asked about using the Turbo-JPEG library from Delphi. I not only gave me the code for loading the DLL, declaring and using the functions in it but when afterwards asked

Is there a more object oriented way to dynamically load a dll and and use it?

It gave me the following answer:

Yes, you can use the LoadLibrary and GetProcAddress functions to dynamically load a DLL and access its functions and procedures in an object-oriented way by defining a class or interface that represents the DLL and its functions and procedures.

Here is an example of how you might define a class to represent the TurboJPEG library and its functions:

type
  TTurboJpeg = class
  private
    hLib: HMODULE;
    tjInitDecompress: tjInitDecompress;
    tjDecompressHeader3: tjDecompressHeader3;
    tjDecompress2: tjDecompress2;
    tjDestroy: tjDestroy;
  public
    constructor Create;
    destructor Destroy; override;
    function InitDecompress(srcBuf: Pointer; const size: Cardinal): tjhandle;
    function DecompressHeader3(handle: tjhandle; srcBuf: Pointer; const size: Cardinal; width: PCardinal; height: PCardinal; jpegSubsamp: PInteger): Integer;
    function Decompress2(handle: tjhandle; srcBuf: Pointer; const size: Cardinal; dstBuf: Pointer; width: Cardinal; pitch: Cardinal; height: Cardinal; pixelFormat: Integer; flags: Integer): Integer;
    procedure Destroy(handle: tjhandle);
  end;

constructor TTurboJpeg.Create;
begin
  inherited Create;
  hLib := LoadLibrary(TurboJpegDLL);
  if hLib = 0 then
    raise Exception.CreateFmt('Error loading %s', [TurboJpegDLL]);
  tjInitDecomp

// somehow the code that should go here is missing. It probably should use GetProcAddress to initialize the tj* fields of the class.

function TTurboJpeg.InitDecompress(srcBuf: Pointer; const size: Cardinal): tjhandle;
begin
  Result := tjInitDecompress(srcBuf, size);
end;

function TTurboJpeg.DecompressHeader3(handle: tjhandle; srcBuf: Pointer; const size: Cardinal; width: PCardinal; height: PCardinal; jpegSubsamp: PInteger): Integer;
begin
  Result := tjDecompressHeader3(handle, srcBuf, size, width, height, jpegSubsamp);
end;

function TTurboJpeg.Decompress2(handle: tjhandle; srcBuf: Pointer; const size: Cardinal; dstBuf: Pointer; width: Cardinal; pitch: Cardinal; height: Cardinal; pixelFormat: Integer; flags: Integer): Integer;
begin
  Result := tjDecompress2(handle, srcBuf, size, dstBuf, width, pitch, height, pixelFormat, flags);
end;

procedure TTurboJpeg.Destroy(handle: tjhandle);
begin
  tjDestroy(handle);
end;

destructor TTurboJpeg.Destroy;
begin
  if hLib <> 0 then
    FreeLibrary(hLib);
  inherited Destroy;
end;

This looks a lot like the class I had written myself in code that is not freely available on the Internet, so I wonder where it got this from. Of course it won’t even compile because the field names of the class are the same as the type declarations for the imported functions and there are probably other bugs in there too.

There were other oddities too:

E.g. it claimed that GExperts has some functionality to extract procedures from code which it clearly does not have (I should know) but which are already part of the Delphi IDE.

Or it used SizeOf() of a pointer to a buffer where it should have passed the size of the buffer rather than that of the pointer.

So, I’d definitely not simply use any code it generates, but it might help a lot by providing some boilerplate code that takes a lot of time to generate but is boring to write.

Overall I was really impressed.

Mastodon Autopost Plugin for WordPress

There are several plugins for WordPress that allow to automatically create a new Mastodon toot (what a stupid name!) for each blog post. I’m using “Mastodon Autopost“. Unfortunately this plugin seems to be outdated and also has a bug that with the current PHP version results in an error/warning message if the post does not have any tags. I was able to fix it though and that fix is amazingly simple.

In line 545 of mastodon_autopost.php there is an if statement using sizeOf() which bombs out because the parameter $post_tags is not an array. Since I am far from being a PHP expert I had to first google that command and found that it isn’t necessary in this case since the foreach that follows later takes care of empty arrays. So the fix looks like this:

    $post_tags = get_the_tags($id);
    // <==== check was here
    if ($post_tags) {
        foreach ($post_tags as $tag) {
            $post_tags_content = $post_tags_content . '#' . preg_replace('/\s+/', '', html_entity_decode($tag->name, ENT_COMPAT, 'UTF-8')) . ' ';
        }
        $post_tags_content = trim($post_tags_content);
    }
    $message_template = str_replace("[tags]", $post_tags_content, $message_template);

If you can see this post on Mastodon, the fix works.

All known Generics bugs fixed in GExperts code formatter

I have just finished fixing all known bugs in the GExperts code formatter that were related to Generics. On top of that I fixed a few others.

I am sure there are still more though, but because I rarely use Generics I haven’t seen them yet. So I need your help: If you have a lot of source code with Generics, please compile a new DLL and try the formatter on that code. Report any bugs on SourceForge.