unit crlfutils; {$I jvcl.inc} interface procedure Run; implementation uses Windows, SysUtils, {$IFNDEF COMPILER6_UP} JvFunctions, {$ENDIF} Classes; function IsTextStream(Stream:TStream):Boolean; const MaxLineLength = 255; var LineBuffer:array[0..MaxLineLength] of Char; CharFlags:array of Word; I, Count:Integer; P, S:PChar; begin Result := False; FillChar(LineBuffer, SizeOf(LineBuffer), 0); Stream.Position := 0; if Stream.Size > MaxLineLength then Count := MaxLineLength else Count := Stream.Size; Stream.Read(LineBuffer, Count); // see if we can come up with an EOL (unless we read the whole file) if Count < Stream.Size then begin P := StrPos(LineBuffer, #13); // try the LF variant too if P = nil then begin P := StrPos(LineBuffer,#10); if P = nil then Exit; end; // terminate the string here P^ := #0; // check if there are any terminators prior to where we expect the EOL S := @LineBuffer; while S < P do begin if S^ = #0 then Exit; Inc(S); end; end; Count := StrLen(LineBuffer); // some editors place a $1A (26) as an EOF marker if LineBuffer[Count - 1] = Char($1A) then begin LineBuffer[Count - 1] := #0; Dec(Count); end; // if first character is $FF, then it's likely not text if LineBuffer[0] = Char($FF) then Exit; // get the char flags SetLength(CharFlags, Count); GetStringTypeEx(LOCALE_USER_DEFAULT, CT_CTYPE1, LineBuffer, Count, CharFlags[0]); // check the CharFlags array to see if anything looks fishy for I := Low(CharFlags) to High(CharFlags) do if ((CharFlags[I] and C1_CNTRL) <> 0) and ((CharFlags[I] and $0F) = 0) then Exit; // best guess is that it looks reasonable Result := True; end; function IsTextFile(const FileName:string):Boolean; var S:TStream; begin Result := False; if not FileExists(FileName) then Exit; S := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone); try Result := IsTextStream(S); finally S.Free; end; end; procedure ShowHelp; begin writeln('==================================================='); writeln('Usage:'); writeln('crlf [options] [options] (etc)'); writeln(''); writeln('where [options] can be any combination of:'); writeln(' /s - recurse into sub-folders from this point on'); writeln(' /c - compare before write: only write if file has changed (default)'); writeln(' /u - do NOT compare before write: always write'); writeln(' /l - convert to Linux line breaks (LF) (default on Linux)'); writeln(' /w - convert to Windows line breaks (CRLF) (default on Windows)'); writeln(''); writeln(' accepts wildcards (* and ?).'); writeln(''); writeln('Example:'); writeln('========'); writeln('crlf /u /l *.pas /c /w /s *.dfm'); writeln('Converts the pas files to LF (always writes) and the dfm files to CRLF (writes if modified). Recurses into sub-folders when searching for dfm''s.'); writeln(''); writeln('Example:'); writeln('========'); writeln('crlf *.pas *.dfm'); writeln('Converts the pas and dfm files to LF on Linux and CRLF on Windows (system default). Always checks before write (no /u option).'); writeln(''); writeln(''); writeln('NOTE: if you compiled using Delphi 5 or earlier, CRLF->LF is NOT supported!'); end; function ConvertFile(const Filename:string;ToWindows,CompareBeforeWrite,Quiet:boolean):boolean; {$IFDEF COMPILER6_UP} const cStyle:array[boolean] of TTextLineBreakStyle = (tlbsLF,tlbsCRLF); {$ENDIF COMPILER6_UP} var F:TFileStream; tmp,tmp2:string; begin // don't convert binary files Result := false; if not IsTextFile(Filename) then Exit; F := TFileStream.Create(Filename,fmOpenReadWrite or fmShareExclusive ); try SetLength(tmp,F.Size); if F.Size > 0 then begin F.Read(tmp[1],F.Size); if CompareBeforeWrite then tmp2 := tmp; tmp := AdjustLineBreaks(tmp{$IFDEF COMPILER6_UP},cStyle[ToWindows]{$ENDIF}); if CompareBeforeWrite and (tmp = tmp2) then begin if not Quiet then writeln(ExtractFilename(Filename), ' not converted'); Exit; end; F.Size := 0; F.Write(tmp[1],Length(tmp)); if not Quiet then writeln(ExtractFilename(Filename), ' converted'); end; finally F.Free; end; Result := true; end; function ConvertFiles(const FileMask:string;ToWindows,CompareBeforeWrite,Recurse,Quiet:boolean):integer; var SearchHandle:DWORD; FindData:TWin32FindData; APath:string; begin Result := 0; APath := ExtractFilePath(Filemask); SearchHandle := FindFirstFile(PChar(Filemask),FindData); if SearchHandle <> INVALID_HANDLE_VALUE then try repeat if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0) then begin if (FindData.dwFileAttributes and FILE_ATTRIBUTE_READONLY = FILE_ATTRIBUTE_READONLY) then writeln('ERROR: ',FindData.cFileName,' is read-only!') else if ConvertFile(APath + FindData.cFileName,ToWindows,CompareBeforeWrite,Quiet) then Inc(Result); end; until not FindNextFile(SearchHandle,FindData); finally Windows.FindClose(SearchHandle); end; // do sub-folders if Recurse then begin SearchHandle := FindFirstFile(PChar(APath + '*.*'),FindData); if SearchHandle <> INVALID_HANDLE_VALUE then try repeat if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = FILE_ATTRIBUTE_DIRECTORY) and (FindData.cFileName[0] <> '.') then Inc(Result,ConvertFiles(IncludeTrailingPathdelimiter(APath + FindData.cFileName) + ExtractFilename(Filemask),ToWindows,CompareBeforeWrite,true,Quiet)); until not FindNextFile(SearchHandle,FindData); finally Windows.FindClose(SearchHandle); end; end; end; procedure Run; const cCurrentOS:array[boolean] of PChar = ('(CRLF->LF)','(LF->CRLF)'); var ToWindows,CompareBeforeWrite,Recurse,Quiet:boolean; i,Count:integer; begin // cmd line: -l *.pas *.dfm *.txt -c -w *.xfm // where // -l - convert CRLF to LF (to linux) // -w - convert LF to CRLF (to windows) // -c - check content: only write if file has changed (default) // -u - never check content: always write writeln(''); writeln('JEDI CR(LF) version 0.1: LF->CRLF and CRLF->LF converter.'); Count := 0; if ParamCount = 0 then begin ShowHelp; Exit; end; CompareBeforeWrite := true; Recurse := false; // set depending on target ToWindows := true; {$IFDEF LINUX} ToWindows := false; {$ENDIF} Quiet := false; for i := 1 to ParamCount do begin if SameText(ParamStr(i),'/l') or SameText(ParamStr(i),'-l') then begin ToWindows := false; if not Quiet then writeln('Converting ', cCurrentOS[ToWindows],':'); Continue; end else if SameText(ParamStr(i),'/w') or SameText(ParamStr(i),'-w') then begin ToWindows := true; if not Quiet then writeln('Converting ', cCurrentOS[ToWindows],':'); Continue; end else if SameText(ParamStr(i),'/?') or SameText(ParamStr(i),'-?') or SameText(ParamStr(i),'/h') or SameText(ParamStr(i),'-h')then begin ShowHelp; Exit; end else if SameText(ParamStr(i),'/c') or SameText(ParamStr(i),'-c') then begin CompareBeforeWrite := true; Continue; end else if SameText(ParamStr(i),'/u') or SameText(ParamStr(i),'-u') then begin CompareBeforeWrite := false; Continue; end else if SameText(ParamStr(i),'/s') or SameText(ParamStr(i),'-s') then begin Recurse := true; Continue; end else if SameText(ParamStr(i),'/q') or SameText(ParamStr(i),'-q') then begin Quiet := true; Continue; end else Inc(Count,ConvertFiles(ExpandUNCFilename(ParamStr(i)),ToWindows,CompareBeforeWrite,Recurse,Quiet)); end; writeln(''); writeln('Done: ', Count, ' files converted.'); end; end.