-- This source is licensed under the Sunset License v1.0 with Ada.Characters.Latin_1, Ada.Characters.Handling, Ada.Exceptions, GNAT.Command_Line, GNAT.Strings, Ada.Command_Line, Ada.Directories, Ada.Strings.Unbounded, Ada.Text_IO, Datatypes, Deck_IO; use Ada.Text_IO, Datatypes; procedure Deck_Convert is package Latin renames Ada.Characters.Latin_1; package Charhand renames Ada.Characters.Handling; package ACom renames Ada.Command_Line; package GCom renames GNAT.Command_Line; package GStr renames GNAT.Strings; package FD renames Ada.Directories; package SU renames Ada.Strings.Unbounded; use type FD.File_Kind; use type SU.Unbounded_String; function "+" (S : in String) return SU.Unbounded_String renames SU.To_Unbounded_String; function "-" (US : in SU.Unbounded_String) return String renames SU.To_String; procedure Strip_Formatting (Notes : in out Note_Vector) is procedure Strip (Text : in out Note; Item : in String) is Position : Natural; begin for F of Text.Fields loop loop Position := SU.Index (Source => SU.Unbounded_String (F), Pattern => Item, Going => Ada.Strings.Forward, Mapping => Charhand.To_Lower'Access); exit when Position = 0; SU.Delete (SU.Unbounded_String (F), Position, Position + Item'Length - 1); end loop; end loop; end Strip; Formatting : array (Positive range <>) of SU.Unbounded_String := (+"", +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", +""); begin for N of Notes loop for S of Formatting loop Strip (N, -S); end loop; end loop; end Strip_Formatting; Config : GCom.Command_Line_Configuration; Further_Help : String := "Try ""deckconv --help"" for more information."; Verbose : aliased Boolean; Help : aliased Boolean; Overwrite : aliased Boolean; Strip_Form : aliased Boolean; Format_Arg : aliased GStr.String_Access; Input_Arg : aliased GStr.String_Access; Output_Arg : aliased GStr.String_Access; Deck_Format : SU.Unbounded_String; Input_Name : SU.Unbounded_String; Output_Name : SU.Unbounded_String; Deck : Deck_IO.Deck_Handle; Models : Model_Map; Notes : Note_Vector; Media : Media_Collection; begin GCom.Define_Switch (Config => Config, Output => Verbose'Access, Switch => "-v", Long_Switch => "--verbose", Help => "chatty output on stderr"); GCom.Define_Switch (Config => Config, Output => Help'Access, Switch => "-h", Long_Switch => "--help", Help => "show this help information"); GCom.Define_Switch (Config => Config, Output => Overwrite'Access, Switch => "-f", Long_Switch => "--force", Help => "overwrite selected output file if present"); GCom.Define_Switch (Config => Config, Output => Format_Arg'Access, Switch => "-t:", Long_Switch => "--type=", Help => "format of output data, options are CSV or FMD, default is FMD"); GCom.Define_Switch (Config => Config, Output => Input_Arg'Access, Switch => "-i:", Long_Switch => "--input=", Help => "file name of input deck"); GCom.Define_Switch (Config => Config, Output => Output_Arg'Access, Switch => "-o:", Long_Switch => "--output=", Help => "base name to store output data"); GCom.Define_Switch (Config => Config, Output => Strip_Form'Access, Switch => "-b", Long_Switch => "--bare", Help => "strip formatting from deck notes"); GCom.Set_Usage (Config => Config, Usage => "[switches]", Help => "Utility to convert Anki flashcard decks to Fresh Memory dictionaries." & Latin.LF & "At minimum input and output options are required." & Latin.LF); begin GCom.Getopt (Config); exception when GCom.Exit_From_Command_Line => ACom.Set_Exit_Status (ACom.Failure); return; when GCom.Invalid_Switch => ACom.Set_Exit_Status (ACom.Failure); return; end; if Format_Arg.all = "" then if Verbose then Put_Line (Standard_Error, "WARNING: No output deck format selected. " & "Proceeding with FMD as default." & Latin.LF); end if; Deck_Format := +"fmd"; else Deck_Format := +(Charhand.To_Lower (Format_Arg.all)); end if; if Deck_Format /= "csv" and Deck_Format /= "fmd" then Put_Line (Standard_Error, "ERROR: Output deck format required. Valid options are CSV or FMD." & Latin.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; if Input_Arg.all = "" then Put_Line (Standard_Error, "ERROR: File name of input deck was not provided." & Latin.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; Input_Name := +(Input_Arg.all); if not FD.Exists (-Input_Name) then Put_Line (Standard_Error, "ERROR: Input deck does not exist." & Latin.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; if Output_Arg.all = "" then Put_Line (Standard_Error, "ERROR: Base file name for output deck was not provided." & Latin.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; Output_Name := +(Output_Arg.all); if FD.Exists ((-Output_Name) & "." & (-Deck_Format)) and not Overwrite then Put_Line (Standard_Error, "ERROR: Output deck file name already exists." & Latin.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; -- Program functionality proper starts here if Verbose then Put (Standard_Error, "Opening input deck database..."); end if; Deck_IO.Open_Database (-Input_Name, Deck); if Verbose then Put_Line (Standard_Error, " Done"); Put (Standard_Error, "Querying models..."); end if; Deck_IO.Query_Models (Deck, Models); if Verbose then Put_Line (Standard_Error, " Done"); Put (Standard_Error, "Querying notes..."); end if; Deck_IO.Query_Notes (Deck, Notes); if Verbose then Put_Line (Standard_Error, " Done"); Put (Standard_Error, "Closing database..."); end if; Deck_IO.Close_Database (Deck); if Verbose then Put_Line (Standard_Error, " Done"); New_Line (Standard_Error); end if; if Strip_Form then if Verbose then Put (Standard_Error, "Stripping formatting from deck notes..."); end if; Strip_Formatting (Notes); if Verbose then Put_Line (Standard_Error, " Done"); New_Line (Standard_Error); end if; end if; declare Contain_Dir : String := FD.Containing_Directory (-Output_Name); Simple_Name : String := FD.Simple_Name (-Output_Name); begin if Deck_Format = "csv" then if Verbose then Put (Standard_Error, "Writing output to CSV..."); end if; Deck_IO.Write_CSV (Contain_Dir, Simple_Name, Models, Notes, Overwrite); if Verbose then Put_Line (Standard_Error, " Done"); end if; elsif Deck_Format = "fmd" then if Verbose then Put (Standard_Error, "Reading media..."); end if; Deck_IO.Read_Media_Collection (-Input_Name, Media); if Verbose then Put_Line (Standard_Error, " Done"); Put (Standard_Error, "Writing output to Fresh Memory Dictionary..."); end if; Deck_IO.Write_FMD (Contain_Dir, Simple_Name, Models, Notes, Media, Overwrite); if Verbose then Put_Line (Standard_Error, " Done"); end if; else raise Constraint_Error; end if; end; exception when Fail : FD.Name_Error => Put_Line (Standard_Error, Latin.LF & "ERROR: " & Ada.Exceptions.Exception_Message (Fail) & Latin.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); end Deck_Convert;