BOM plugins refactor

Extended BOM_PLUGIN to a class and moved the plugin construction related code there.
This commit is contained in:
Maciej Suminski 2018-11-12 14:45:07 +01:00
parent 240051fb26
commit 15772e4b0b
4 changed files with 314 additions and 254 deletions

View File

@ -130,6 +130,7 @@ set( EESCHEMA_SRCS
${EESCHEMA_DLGS}
${EESCHEMA_WIDGETS}
${EESCHEMA_LIBEDIT_SRCS}
bom_plugins.cpp
sch_view.cpp
sch_painter.cpp
annotate.cpp

90
eeschema/bom_plugins.cpp Normal file
View File

@ -0,0 +1,90 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2018 CERN
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* https://www.gnu.org/licenses/gpl-3.0.html
* or you may search the http://www.gnu.org website for the version 3 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "bom_plugins.h"
BOM_PLUGIN::BOM_PLUGIN( const wxString& aFile )
: m_file( aFile )
{
if( !wxFile::Exists( aFile ) )
throw std::runtime_error( _( "Cannot open plugin " ) + aFile.ToStdString() );
m_name = m_file.GetName();
wxString extension = m_file.GetExt().Lower();
if( extension == "xsl" )
{
m_info = readHeader( "-->" );
m_cmd = wxString::Format( "xsltproc -o \"%%O\" \"%s\" \"%%I\"", m_file.GetFullPath() );
}
else if( extension == "py" )
{
m_info = readHeader( "\"\"\"" );
m_cmd = wxString::Format( "python \"%s\" \"%%I\" \"%%O\"", m_file.GetFullPath() );
}
#ifdef __WINDOWS__
else if( extension == "pyw" )
{
m_info = readHeader( "\"\"\"" );
m_cmd = wxString::Format( "pythonw \"%s\" \"%%I\" \"%%O\"", m_file.GetFullPath() );
}
#endif /* __WINDOWS__ */
else // fallback
{
m_cmd = m_file.GetFullPath();
}
}
wxString BOM_PLUGIN::readHeader( const wxString& aEndSection )
{
if( aEndSection.IsEmpty() )
return wxEmptyString;
wxFile fdata( m_file.GetFullPath() ); // dtor will close the file
wxString data;
if( !fdata.ReadAll( &data ) )
return wxEmptyString;
const wxString header( "@package" );
// Extract substring between @package and endsection
int strstart = data.Find( header );
if( strstart == wxNOT_FOUND )
return wxEmptyString;
strstart += header.Length();
int strend = data.find( aEndSection, strstart );
if( strend == wxNOT_FOUND)
return wxEmptyString;
// Remove empty line if any
while( data[strstart] < ' ' )
strstart++;
return data.SubString( strstart, strend - 1 );
}

127
eeschema/bom_plugins.h Normal file
View File

@ -0,0 +1,127 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2018 CERN
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* https://www.gnu.org/licenses/gpl-3.0.html
* or you may search the http://www.gnu.org website for the version 3 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef BOM_PLUGINS_H
#define BOM_PLUGINS_H
#include <wx/arrstr.h>
#include <wx/file.h>
#include <wx/filename.h>
#include <memory>
/**
* Class representing a Bill of Material output plugin.
*/
class BOM_PLUGIN
{
public:
typedef std::unique_ptr<BOM_PLUGIN> PTR;
/**
* Constructor.
* @param aFile is path to the plugin file.
*/
BOM_PLUGIN( const wxString& aFile );
/**
* Returns plugin description stored in the plugin header file (if available).
*/
const wxString& GetInfo() const
{
return m_info;
}
/**
* Returns the file name of the plugin.
*/
const wxFileName& GetFile() const
{
return m_file;
}
/**
* Returns the customisable plugin name.
*/
const wxString& GetName() const
{
return m_name;
}
/**
* Sets the customisable plugin name.
* @param aName is the new name.
*/
void SetName( const wxString& aName )
{
m_name = aName;
}
/**
* Returns the command to execute the plugin.
*/
const wxString& GetCommand() const
{
return m_cmd;
}
/**
* Sets the command to execute the plugin.
*/
void SetCommand( const wxString& aCommand )
{
m_cmd = aCommand;
}
/**
* Accessor to array of options.
*/
wxArrayString& Options()
{
return m_options;
}
protected:
/**
* Reads the plugin file header.
* @param aEndSection is a string marking end of the header.
*/
wxString readHeader( const wxString& aEndSection );
///> Path to the plugin
const wxFileName m_file;
///> User customisable name
wxString m_name;
///> Command to execute the plugin
wxString m_cmd;
///> Description of the plugin (normally from the plugin header)
wxString m_info;
wxArrayString m_options;
};
#endif /* BOM_PLUGINS_H */

View File

@ -42,11 +42,13 @@
#include <dialog_bom_base.h>
#include <html_messagebox.h>
#include <reporter.h>
#include <bom_plugins.h>
#include <make_unique.h>
#define BOM_PLUGINS_KEY wxT("bom_plugins")
#define BOM_PLUGIN_SELECTED_KEY wxT("bom_plugin_selected")
static constexpr wxChar BOM_PLUGINS_KEY[] = wxT( "bom_plugins" );
static constexpr wxChar BOM_PLUGIN_SELECTED_KEY[] = wxT( "bom_plugin_selected" );
const char * s_bomHelpInfo =
static const char* s_bomHelpInfo =
#include <dialog_bom_help_html.h>
;
@ -54,24 +56,7 @@ const char * s_bomHelpInfo =
using namespace T_BOMCFG_T;
/**
* Hold data of the BOM plugin.
*/
struct BOM_PLUGIN
{
wxString Name;
wxString Command;
wxArrayString Options;
};
/**
* Define wxArray of BOM_PLUGIN.
*/
WX_DECLARE_OBJARRAY( BOM_PLUGIN, BOM_PLUGIN_ARRAY );
#include <wx/arrimpl.cpp>
WX_DEFINE_OBJARRAY( BOM_PLUGIN_ARRAY )
typedef std::vector<BOM_PLUGIN::PTR> BOM_PLUGIN_ARRAY;
/**
@ -129,10 +114,9 @@ void BOM_CFG_PARSER::Parse()
void BOM_CFG_PARSER::parsePlugin()
{
BOM_PLUGIN plugin;
NeedSYMBOLorNUMBER();
plugin.Name = FromUTF8();
wxString name = FromUTF8();
auto plugin = std::make_unique<BOM_PLUGIN>( name );
T token;
@ -148,13 +132,19 @@ void BOM_CFG_PARSER::parsePlugin()
case T_cmd:
NeedSYMBOLorNUMBER();
plugin.Command = FromUTF8();
if( plugin )
plugin->SetCommand( FromUTF8() );
NeedRIGHT();
break;
case T_opts:
NeedSYMBOLorNUMBER();
plugin.Options.Add( FromUTF8() );
if( plugin )
plugin->Options().Add( FromUTF8() );
NeedRIGHT();
break;
@ -164,8 +154,8 @@ void BOM_CFG_PARSER::parsePlugin()
}
}
if( ! plugin.Name.IsEmpty() )
m_pluginsList->Add( plugin );
if( plugin )
m_pluginsList->push_back( std::move( plugin ) );
}
@ -197,13 +187,15 @@ private:
void pluginInit();
void installPluginsList();
wxString getPluginFileName( const wxString& aCommand );
BOM_PLUGIN* selectedPlugin()
{
int idx = m_lbPlugins->GetSelection();
/*
* Display the text found between the keyword @package (compatible with doxygen comments)
* and the end of comment block (""" in python, --> in xml)
*/
void displayPluginInfo( FILE * aFile, const wxString& aFilename );
if( idx < 0 || idx >= m_plugins.size() )
return nullptr;
return m_plugins[idx].get();
}
wxString choosePlugin();
};
@ -258,16 +250,16 @@ DIALOG_BOM::~DIALOG_BOM()
STRING_FORMATTER writer;
writer.Print( 0, "(plugins" );
for( unsigned ii = 0; ii < m_plugins.GetCount(); ii++ )
for( auto& plugin : m_plugins )
{
writer.Print( 1, "(plugin %s (cmd %s)",
writer.Quotew( m_plugins.Item( ii ).Name ).c_str(),
writer.Quotew( m_plugins.Item( ii ).Command ).c_str() );
writer.Quotew( plugin->GetFile().GetFullPath() ).c_str(),
writer.Quotew( plugin->GetCommand() ).c_str() );
for( unsigned jj = 0; jj < m_plugins.Item( ii ).Options.GetCount(); jj++ )
for( unsigned jj = 0; jj < plugin->Options().GetCount(); jj++ )
{
writer.Print( 1, "(opts %s)",
writer.Quotew( m_plugins.Item( ii ).Options.Item( jj ) ).c_str() );
writer.Quotew( plugin->Options().Item( jj ) ).c_str() );
}
writer.Print( 0, ")" );
@ -281,7 +273,6 @@ DIALOG_BOM::~DIALOG_BOM()
wxString active_plugin_name = m_lbPlugins->GetStringSelection( );
m_config->Write( BOM_PLUGIN_SELECTED_KEY, active_plugin_name );
}
@ -304,14 +295,18 @@ void DIALOG_BOM::installPluginsList()
{
// wxLogMessage( ioe.What() );
}
catch( std::runtime_error& e )
{
DisplayError( nullptr, e.what() );
}
}
// Populate list box
for( unsigned ii = 0; ii < m_plugins.GetCount(); ii++ )
for( unsigned ii = 0; ii < m_plugins.size(); ii++ )
{
m_lbPlugins->Append( m_plugins.Item( ii ).Name );
m_lbPlugins->Append( m_plugins[ii]->GetName() );
if( active_plugin_name == m_plugins.Item( ii ).Name )
if( active_plugin_name == m_plugins[ii]->GetName() )
m_lbPlugins->SetSelection( ii );
}
@ -327,86 +322,27 @@ void DIALOG_BOM::OnPluginSelected( wxCommandEvent& event )
void DIALOG_BOM::pluginInit()
{
int ii = m_lbPlugins->GetSelection();
auto plugin = selectedPlugin();
if( ii < 0 )
if( !plugin )
{
m_textCtrlName->SetValue( wxEmptyString );
m_textCtrlCommand->SetValue( wxEmptyString );
m_Messages->SetValue( wxEmptyString );
return;
}
m_textCtrlName->SetValue( m_plugins.Item( ii ).Name );
m_textCtrlCommand->SetValue( m_plugins.Item( ii ).Command );
m_textCtrlName->SetValue( plugin->GetName() );
m_textCtrlCommand->SetValue( plugin->GetCommand() );
m_Messages->SetValue( plugin->GetInfo() );
m_Messages->SetSelection( 0, 0 );
#ifdef __WINDOWS__
if( m_plugins.Item( ii ).Options.Index( wxT( "show_console" ) ) == wxNOT_FOUND )
if( plugin.Options().Index( wxT( "show_console" ) ) == wxNOT_FOUND )
m_checkBoxShowConsole->SetValue( false );
else
m_checkBoxShowConsole->SetValue( true );
#endif
wxString pluginName = getPluginFileName( m_textCtrlCommand->GetValue() );
if( pluginName.IsEmpty() )
return;
FILE* pluginFile = wxFopen( pluginName, "rt" );
if( !pluginFile )
{
DisplayError( this, wxString::Format( _( "Failed to open file \"%s\"." ), pluginName ) );
return;
}
displayPluginInfo( pluginFile, pluginName );
}
void DIALOG_BOM::displayPluginInfo( FILE * aFile, const wxString& aFilename )
{
m_Messages->Clear();
// Display the text found between the keyword @package (compatible with doxygen comments)
// and the end of comment block (""" in python, --> in xml)
wxString data;
wxFFile fdata( aFile ); // dtor will close the file
if( !fdata.ReadAll( &data ) )
return;
wxString header( "@package" );
wxString endsection;
wxFileName fn( aFilename );
if( fn.GetExt().IsSameAs( "py", false ) )
endsection = "\"\"\"";
else if( fn.GetExt().IsSameAs( "xsl", false ) )
endsection = "-->";
else
// If this is not a python or xsl file, then we don't know how to find the info
return;
// Extract substring between @package and endsection
int strstart = data.Find( header );
if( strstart == wxNOT_FOUND )
return;
strstart += header.Length();
int strend = data.find( endsection, strstart );
if( strend == wxNOT_FOUND)
return;
// Remove empty line if any
while( data[strstart] < ' ' )
strstart++;
m_Messages->SetValue( data.SubString( strstart, strend-1 ) );
m_Messages->SetSelection( 0, 0 );
}
@ -444,8 +380,7 @@ void DIALOG_BOM::OnRemovePlugin( wxCommandEvent& event )
return;
m_lbPlugins->Delete( ii );
m_plugins.RemoveAt( ii );
m_plugins.erase( m_plugins.begin() + ii );
// Select the next item, if exists
if( m_lbPlugins->GetCount() )
@ -457,14 +392,13 @@ void DIALOG_BOM::OnRemovePlugin( wxCommandEvent& event )
void DIALOG_BOM::OnAddPlugin( wxCommandEvent& event )
{
wxString cmdLine = choosePlugin();
BOM_PLUGIN newPlugin;
wxString filename = choosePlugin();
if( cmdLine.IsEmpty() )
if( filename.IsEmpty() )
return;
// Creates a new plugin entry
wxFileName fn( getPluginFileName( cmdLine ) );
wxFileName fn( filename );
wxString name = wxGetTextFromUser( _( "Plugin nickname:" ), _( "Add Plugin" ),
fn.GetName(), this );
@ -472,31 +406,40 @@ void DIALOG_BOM::OnAddPlugin( wxCommandEvent& event )
return;
// Verify if it does not exists
for( unsigned ii = 0; ii < m_plugins.GetCount(); ii++ )
for( unsigned ii = 0; ii < m_plugins.size(); ii++ )
{
if( name == m_plugins.Item( ii ).Name )
if( name == m_plugins[ii]->GetName() )
{
wxMessageBox( wxString::Format( _( "Nickname \"%s\" already in use." ), name ) );
return;
}
}
// Append the new plugin
newPlugin.Name = name;
newPlugin.Command = wxEmptyString;
m_plugins.Add( newPlugin );
m_lbPlugins->Append( name );
m_lbPlugins->SetSelection( m_lbPlugins->GetCount() - 1 );
m_textCtrlCommand->SetValue( cmdLine );
try
{
auto plugin = std::make_unique<BOM_PLUGIN>( fn.GetFullPath() );
pluginInit();
if( !plugin )
return;
plugin->SetName( name );
m_lbPlugins->Append( name );
m_lbPlugins->SetSelection( m_lbPlugins->GetCount() - 1 );
m_textCtrlCommand->SetValue( plugin->GetCommand() );
m_plugins.push_back( std::move( plugin ) );
pluginInit();
}
catch( const std::runtime_error& e )
{
DisplayError( this, e.what() );
}
}
wxString DIALOG_BOM::choosePlugin()
{
static wxString lastPath;
wxString mask = "*";
if( lastPath.IsEmpty() )
{
@ -507,135 +450,33 @@ wxString DIALOG_BOM::choosePlugin()
#endif
}
wxString fullFileName = EDA_FILE_SELECTOR( _( "Plugin files:" ),
lastPath,
wxEmptyString,
wxEmptyString,
mask,
this,
wxFD_OPEN,
true );
if( fullFileName.IsEmpty() )
return wxEmptyString;
wxString fullFileName = EDA_FILE_SELECTOR( _( "Plugin files:" ), lastPath, wxEmptyString,
wxEmptyString, "*", this, wxFD_OPEN, true );
// Creates a default command line,
// suitable to run the external tool xslproc or python
// The default command line depending on plugin extension, currently
// "xsl" or "exe" or "py" or "pyw"
wxString cmdLine;
wxFileName fn( fullFileName );
wxString ext = fn.GetExt();
lastPath = fn.GetPath();
// Python requires on Windows the path of the script ends by '/' instead of '\'
// otherwise import does not find modules in the same folder as the python script
// We cannot change all '\' to '/' in command line because it causes issues with files
// that are stored on network resources and pointed to using the Windows
// Universal Naming Convention format. (full filename starting by \\server\)
//
// I hope changing the last separator only to '/' will work.
#ifdef __WINDOWS__
if( ext == "py" || ext == "pyw" )
{
wxString sc_path = fn.GetPathWithSep();
sc_path.RemoveLast();
fullFileName = sc_path + '/' + fn.GetFullName();;
}
#endif
if( ext == "xsl" )
cmdLine.Printf( "xsltproc -o \"%%O\" \"%s\" \"%%I\"", GetChars( fullFileName ) );
else if( ext == "exe" )
cmdLine.Printf( "\"%s\" < \"%%I\" > \"%%O\"", GetChars( fullFileName ) );
else if( ext == "py" )
cmdLine.Printf( "python \"%s\" \"%%I\" \"%%O\"", GetChars( fullFileName ) );
else if( ext == "pyw" )
#ifdef __WINDOWS__
cmdLine.Printf( "pythonw \"%s\" \"%%I\" \"%%O\"", GetChars( fullFileName ) );
#else
cmdLine.Printf( "python \"%s\" \"%%I\" \"%%O\"", GetChars( fullFileName ) );
#endif
else
cmdLine.Printf( "\"%s\"", GetChars( fullFileName ) );
return cmdLine;
return fullFileName;
}
wxString DIALOG_BOM::getPluginFileName( const wxString& aCommand )
{
wxString pluginName;
// Try to find the plugin name.
// This is possible if the name ends by .py or .pyw or .xsl or .exe
int pos = -1;
if( (pos = aCommand.Find( ".pyw" )) != wxNOT_FOUND )
pos += 3;
else if( (pos = aCommand.Find( ".py" )) != wxNOT_FOUND )
pos += 2;
else if( (pos = aCommand.Find( ".xsl" )) != wxNOT_FOUND )
pos += 3;
else if( (pos = aCommand.Find( ".exe" )) != wxNOT_FOUND )
pos += 3;
// the end of plugin name is at position pos.
if( pos > 0 )
{
// Be sure this is the end of the name: the next char is " or space
int eos = aCommand[pos+1];
if( eos == ' '|| eos == '\"' )
{
// search for the starting point of the name
int jj = pos-1;
while( jj >= 0 )
if( aCommand[jj] != eos )
jj--;
else
break;
// extract the name
if( jj >= 0 )
{
eos = aCommand[jj];
if( eos == ' '|| eos == '\"' ) // do not include delimiters
jj++;
pluginName = aCommand.SubString( jj, pos );
}
}
}
// Using a format like %P is possible in plugin name, so expand it
wxString prj_dir = Prj().GetProjectPath();
if( prj_dir.EndsWith( '/' ) || prj_dir.EndsWith( '\\' ) )
prj_dir.RemoveLast();
pluginName.Replace( "%P", prj_dir.GetData(), true );
return pluginName;
}
void DIALOG_BOM::OnEditPlugin( wxCommandEvent& event )
{
wxString pluginName = getPluginFileName( m_textCtrlCommand->GetValue() );
auto plugin = selectedPlugin();
if( pluginName.Length() <= 2 ) // if name != ""
if( !plugin )
return;
wxString pluginFile = plugin->GetFile().GetFullPath();
if( pluginFile.Length() <= 2 ) // if name != ""
{
wxMessageBox( _( "Plugin file name not found." ) );
return;
}
AddDelimiterString( pluginName );
AddDelimiterString( pluginFile );
wxString editorname = Pgm().GetEditorName();
if( !editorname.IsEmpty() )
ExecuteFile( this, editorname, pluginName );
ExecuteFile( this, editorname, pluginFile );
else
wxMessageBox( _( "No text editor selected in KiCad. Please choose one." ) );
}
@ -643,7 +484,7 @@ void DIALOG_BOM::OnEditPlugin( wxCommandEvent& event )
void DIALOG_BOM::OnHelp( wxCommandEvent& event )
{
HTML_MESSAGE_BOX help_Dlg( this, _( "Bom Generation Help" ) );
HTML_MESSAGE_BOX help_Dlg( this, _( "Bill of Material Generation Help" ) );
help_Dlg.SetDialogSizeInDU( 500, 350 );
wxString msg = FROM_UTF8( s_bomHelpInfo );
@ -654,12 +495,10 @@ void DIALOG_BOM::OnHelp( wxCommandEvent& event )
void DIALOG_BOM::OnCommandLineEdited( wxCommandEvent& event )
{
int ii = m_lbPlugins->GetSelection();
auto plugin = selectedPlugin();
if( ii < 0 )
return;
m_plugins.Item( ii ).Command = m_textCtrlCommand->GetValue();
if( plugin )
plugin->SetCommand( m_textCtrlCommand->GetValue() );
}
@ -670,26 +509,29 @@ void DIALOG_BOM::OnNameEdited( wxCommandEvent& event )
if( ii < 0 )
return;
m_plugins.Item( ii ).Name = m_textCtrlName->GetValue();
m_lbPlugins->SetString( ii, m_plugins.Item( ii ).Name );
m_plugins[ii]->SetName( m_textCtrlName->GetValue() );
m_lbPlugins->SetString( ii, m_plugins[ii]->GetName() );
}
void DIALOG_BOM::OnShowConsoleChanged( wxCommandEvent& event )
{
#ifdef __WINDOWS__
int ii = m_lbPlugins->GetSelection();
static constexpr wxChar OPT_SHOW_CONSOLE[] = wxT( "show_console" );
if( ii < 0 )
auto plugin = selectedPlugin();
if( !plugin )
return;
if( m_checkBoxShowConsole->IsChecked() )
{
if( m_plugins.Item( ii ).Options.Index( "show_console" ) == wxNOT_FOUND )
m_plugins.Item( ii ).Options.Add( "show_console" );
if( plugin->Options().Index( OPT_SHOW_CONSOLE ) == wxNOT_FOUND )
plugin->Options().Add( OPT_SHOW_CONSOLE );
}
else
{
m_plugins.Item( ii ).Options.Remove( "show_console" );
plugin->Options().Remove( OPT_SHOW_CONSOLE );
}
#endif
}