KiCad PCB EDA Suite
Loading...
Searching...
No Matches
api_plugin.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2024 Jon Evans <[email protected]>
5 * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include <magic_enum.hpp>
22#include <nlohmann/json.hpp>
23#include <wx/log.h>
24#include <wx/regex.h>
25#include <wx/stdstream.h>
26#include <wx/wfstream.h>
27
28#include <api/api_plugin.h>
30#include <json_conversions.h>
31
32
33bool PLUGIN_RUNTIME::FromJson( const nlohmann::json& aJson )
34{
35 // TODO move to tl::expected and give user feedback about parse errors
36
37 try
38 {
39 type = magic_enum::enum_cast<PLUGIN_RUNTIME_TYPE>( aJson.at( "type" ).get<std::string>(),
40 magic_enum::case_insensitive )
41 .value_or( PLUGIN_RUNTIME_TYPE::INVALID );
42
43
44 }
45 catch( ... )
46 {
47 return false;
48 }
49
50 return type != PLUGIN_RUNTIME_TYPE::INVALID;
51}
52
53
55{
56 API_PLUGIN_CONFIG( API_PLUGIN& aParent, const wxFileName& aConfigFile );
57
58 bool valid;
59 wxString identifier;
60 wxString name;
61 wxString description;
63 std::vector<PLUGIN_ACTION> actions;
64
66};
67
68
69API_PLUGIN_CONFIG::API_PLUGIN_CONFIG( API_PLUGIN& aParent, const wxFileName& aConfigFile ) :
70 parent( aParent )
71{
72 valid = false;
73
74 if( !aConfigFile.IsFileReadable() )
75 return;
76
77 wxLogTrace( traceApi, "Plugin: parsing config file" );
78
79 wxFFileInputStream fp( aConfigFile.GetFullPath(), wxT( "rt" ) );
80 wxStdInputStream fstream( fp );
81
82 nlohmann::json js;
83
84 try
85 {
86 js = nlohmann::json::parse( fstream, nullptr,
87 /* allow_exceptions = */ true,
88 /* ignore_comments = */ true );
89 }
90 catch( ... )
91 {
92 wxLogTrace( traceApi, "Plugin: exception during parse" );
93 return;
94 }
95
96 // TODO add schema and validate
97
98 // All of these are required; any exceptions here leave us with valid == false
99 try
100 {
101 identifier = js.at( "identifier" ).get<wxString>();
102 name = js.at( "name" ).get<wxString>();
103 description = js.at( "description" ).get<wxString>();
104
105 if( !runtime.FromJson( js.at( "runtime" ) ) )
106 {
107 wxLogTrace( traceApi, "Plugin: error parsing runtime section" );
108 return;
109 }
110 }
111 catch( ... )
112 {
113 wxLogTrace( traceApi, "Plugin: exception while parsing required keys" );
114 return;
115 }
116
117 // At minimum, we need a reverse-DNS style identifier with two dots and a 2+ character TLD
118 wxRegEx identifierRegex( wxS( "[\\w\\d]{2,}\\.[\\w\\d]+\\.[\\w\\d]+" ) );
119
120 if( !identifierRegex.Matches( identifier ) )
121 {
122 wxLogTrace( traceApi, wxString::Format( "Plugin: identifier %s does not meet requirements",
123 identifier ) );
124 return;
125 }
126
127 wxLogTrace( traceApi, wxString::Format( "Plugin: %s (%s)", identifier, name ) );
128
129 try
130 {
131 const nlohmann::json& actionsJs = js.at( "actions" );
132
133 if( actionsJs.is_array() )
134 {
135 for( const nlohmann::json& actionJs : actionsJs )
136 {
137 if( std::optional<PLUGIN_ACTION> a = parent.createActionFromJson( actionJs ) )
138 {
139 a->identifier = wxString::Format( "%s.%s", identifier, a->identifier );
140 wxLogTrace( traceApi, wxString::Format( "Plugin: loaded action %s",
141 a->identifier ) );
142 actions.emplace_back( *a );
143 }
144 }
145 }
146 }
147 catch( ... )
148 {
149 wxLogTrace( traceApi, "Plugin: exception while parsing actions" );
150 }
151
152 valid = true;
153}
154
155
156API_PLUGIN::API_PLUGIN( const wxFileName& aConfigFile ) :
157 m_configFile( aConfigFile ),
158 m_config( std::make_unique<API_PLUGIN_CONFIG>( *this, aConfigFile ) )
159{
160}
161
162
164{
165}
166
167
169{
170 return m_config->valid;
171}
172
173
174const wxString& API_PLUGIN::Identifier() const
175{
176 return m_config->identifier;
177}
178
179
180const wxString& API_PLUGIN::Name() const
181{
182 return m_config->name;
183}
184
185
186const wxString& API_PLUGIN::Description() const
187{
188 return m_config->description;
189}
190
191
193{
194 return m_config->runtime;
195}
196
197
198const std::vector<PLUGIN_ACTION>& API_PLUGIN::Actions() const
199{
200 return m_config->actions;
201}
202
203
204wxString API_PLUGIN::BasePath() const
205{
206 return m_configFile.GetPath();
207}
208
209
210std::optional<PLUGIN_ACTION> API_PLUGIN::createActionFromJson( const nlohmann::json& aJson )
211{
212 // TODO move to tl::expected and give user feedback about parse errors
213 PLUGIN_ACTION action( *this );
214
215 try
216 {
217 action.identifier = aJson.at( "identifier" ).get<wxString>();
218 wxLogTrace( traceApi, wxString::Format( "Plugin: load action %s", action.identifier ) );
219 action.name = aJson.at( "name" ).get<wxString>();
220 action.description = aJson.at( "description" ).get<wxString>();
221 action.entrypoint = aJson.at( "entrypoint" ).get<wxString>();
222 action.show_button = aJson.contains( "show-button" ) && aJson.at( "show-button" ).get<bool>();
223 }
224 catch( ... )
225 {
226 wxLogTrace( traceApi, "Plugin: exception while parsing action required keys" );
227 return std::nullopt;
228 }
229
230 wxFileName f( action.entrypoint );
231
232 if( !f.IsRelative() )
233 {
234 wxLogTrace( traceApi, wxString::Format( "Plugin: action contains abs path %s; skipping",
235 action.entrypoint ) );
236 return std::nullopt;
237 }
238
239 f.Normalize( wxPATH_NORM_ABSOLUTE, m_configFile.GetPath() );
240
241 if( !f.IsFileReadable() )
242 {
243 wxLogTrace( traceApi, wxString::Format( "WARNING: action entrypoint %s is not readable",
244 f.GetFullPath() ) );
245 }
246
247 if( aJson.contains( "args" ) && aJson.at( "args" ).is_array() )
248 {
249 for( const nlohmann::json& argJs : aJson.at( "args" ) )
250 {
251 try
252 {
253 action.args.emplace_back( argJs.get<wxString>() );
254 }
255 catch( ... )
256 {
257 wxLogTrace( traceApi, "Plugin: exception while parsing action args" );
258 continue;
259 }
260 }
261 }
262
263 if( aJson.contains( "scopes" ) && aJson.at( "scopes" ).is_array() )
264 {
265 for( const nlohmann::json& scopeJs : aJson.at( "scopes" ) )
266 {
267 try
268 {
269 action.scopes.insert( magic_enum::enum_cast<PLUGIN_ACTION_SCOPE>(
270 scopeJs.get<std::string>(), magic_enum::case_insensitive )
271 .value_or( PLUGIN_ACTION_SCOPE::INVALID ) );
272 }
273 catch( ... )
274 {
275 wxLogTrace( traceApi, "Plugin: exception while parsing action scopes" );
276 continue;
277 }
278 }
279 }
280
281 auto handleBitmap =
282 [&]( const std::string& aKey, wxBitmapBundle& aDest )
283 {
284 if( aJson.contains( aKey ) && aJson.at( aKey ).is_array() )
285 {
286 wxVector<wxBitmap> bitmaps;
287
288 for( const nlohmann::json& iconJs : aJson.at( aKey ) )
289 {
290 wxFileName iconFile;
291
292 try
293 {
294 iconFile = iconJs.get<wxString>();
295 }
296 catch( ... )
297 {
298 continue;
299 }
300
301 iconFile.Normalize( wxPATH_NORM_ABSOLUTE, m_configFile.GetPath() );
302
303 wxLogTrace( traceApi,
304 wxString::Format( "Plugin: action %s: loading icon %s",
305 action.identifier, iconFile.GetFullPath() ) );
306
307
308 if( !iconFile.IsFileReadable() )
309 {
310 wxLogTrace( traceApi, "Plugin: icon file could not be read" );
311 continue;
312 }
313
314 wxBitmap bmp;
315 // TODO: If necessary; support types other than PNG
316 bmp.LoadFile( iconFile.GetFullPath(), wxBITMAP_TYPE_PNG );
317
318 if( bmp.IsOk() )
319 bitmaps.push_back( bmp );
320 else
321 wxLogTrace( traceApi, "Plugin: icon file not a valid bitmap" );
322 }
323
324 aDest = wxBitmapBundle::FromBitmaps( bitmaps );
325 }
326 };
327
328 handleBitmap( "icons-light", action.icon_light );
329 handleBitmap( "icons-dark", action.icon_dark );
330
331 return action;
332}
A plugin that is invoked by KiCad and runs as an external process; communicating with KiCad via the I...
Definition: api_plugin.h:105
const PLUGIN_RUNTIME & Runtime() const
Definition: api_plugin.cpp:192
API_PLUGIN(const wxFileName &aConfigFile)
Definition: api_plugin.cpp:156
const wxString & Name() const
Definition: api_plugin.cpp:180
const std::vector< PLUGIN_ACTION > & Actions() const
Definition: api_plugin.cpp:198
const wxString & Identifier() const
Definition: api_plugin.cpp:174
std::unique_ptr< API_PLUGIN_CONFIG > m_config
Definition: api_plugin.h:128
wxFileName m_configFile
Definition: api_plugin.h:126
bool IsOk() const
Definition: api_plugin.cpp:168
wxString BasePath() const
Definition: api_plugin.cpp:204
const wxString & Description() const
Definition: api_plugin.cpp:186
std::optional< PLUGIN_ACTION > createActionFromJson(const nlohmann::json &aJson)
Definition: api_plugin.cpp:210
const wxChar *const traceApi
Flag to enable debug output related to the API plugin system.
STL namespace.
wxString description
Definition: api_plugin.cpp:61
API_PLUGIN_CONFIG(API_PLUGIN &aParent, const wxFileName &aConfigFile)
Definition: api_plugin.cpp:69
PLUGIN_RUNTIME runtime
Definition: api_plugin.cpp:62
API_PLUGIN & parent
Definition: api_plugin.cpp:65
wxString identifier
Definition: api_plugin.cpp:59
std::vector< PLUGIN_ACTION > actions
Definition: api_plugin.cpp:63
An action performed by a plugin via the IPC API (not to be confused with ACTION_PLUGIN,...
Definition: api_plugin.h:81
wxBitmapBundle icon_light
Definition: api_plugin.h:93
wxString name
Definition: api_plugin.h:87
wxString description
Definition: api_plugin.h:88
std::set< PLUGIN_ACTION_SCOPE > scopes
Definition: api_plugin.h:91
wxString identifier
Definition: api_plugin.h:86
wxString entrypoint
Definition: api_plugin.h:90
wxBitmapBundle icon_dark
Definition: api_plugin.h:94
std::vector< wxString > args
Definition: api_plugin.h:92
bool show_button
Definition: api_plugin.h:89
bool FromJson(const nlohmann::json &aJson)
Definition: api_plugin.cpp:33
PLUGIN_RUNTIME_TYPE type
Definition: api_plugin.h:69