KiCad PCB EDA Suite
Loading...
Searching...
No Matches
project_file_patch.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
26
27#include <json_common.h>
28#include <kiplatform/io.h>
29
30#include <wx/filefn.h>
31#include <wx/log.h>
32#include <wx/stdstream.h>
33#include <wx/wfstream.h>
34
35#include <unordered_map>
36
37
38namespace KICAD_DIFF
39{
40
41namespace
42{
43
48std::optional<std::string> docPropToPointer( const wxString& aDocProp, DOC_KIND aKind )
49{
50 static const std::unordered_map<wxString, std::string> table = {
51 // BOARD_DESIGN_SETTINGS is a NESTED_SETTINGS instance registered at
52 // "design_settings" under "board"; its rule_severities param therefore
53 // serializes to /board/design_settings/rule_severities.
54 { DOC_PROP_DRC_SEVERITIES, "/board/design_settings/rule_severities" },
55
56 // ERC severities are a NESTED_SETTINGS instance registered at "erc".
57 { DOC_PROP_ERC_SEVERITIES, "/erc" },
58
59 // NET_SETTINGS is a NESTED_SETTINGS instance registered at "net_settings".
60 { DOC_PROP_NET_CLASSES, "/net_settings" },
61 };
62
63 if( aDocProp == DOC_PROP_DRAWING_SHEET )
64 {
65 if( aKind == DOC_KIND::SCH )
66 return "/schematic/page_layout_descr_file";
67
68 return "/pcbnew/page_layout_descr_file";
69 }
70
71 auto it = table.find( aDocProp );
72
73 if( it == table.end() )
74 return std::nullopt;
75
76 return it->second;
77}
78
79} // namespace
80
81
82std::optional<std::string> DocPropJsonPointer( const wxString& aDocProp, DOC_KIND aKind )
83{
84 return docPropToPointer( aDocProp, aKind );
85}
86
87
88std::optional<std::string> DocPropJsonPointer( const wxString& aDocProp )
89{
90 return DocPropJsonPointer( aDocProp, DOC_KIND::PCB );
91}
92
93
94bool ApplyProjectFilePatch( nlohmann::json& aTarget,
95 const nlohmann::json& aSource,
96 const wxString& aDocProp,
97 DOC_KIND aKind )
98{
99 std::optional<std::string> pointer = DocPropJsonPointer( aDocProp, aKind );
100
101 if( !pointer )
102 return false;
103
104 try
105 {
106 nlohmann::json::json_pointer ptr( *pointer );
107
108 if( !aSource.contains( ptr ) )
109 return false;
110
111 // The assignment resolves the pointer against aTarget and can throw when
112 // an intermediate node is not an object (corrupt output file); keep it
113 // inside the guard so a malformed target degrades to the caller's full
114 // SaveProjectCopy fallback rather than escaping uncaught.
115 aTarget[ptr] = aSource[ptr];
116 }
117 catch( const std::exception& )
118 {
119 return false;
120 }
121
122 return true;
123}
124
125
126bool ApplyProjectFilePatch( nlohmann::json& aTarget,
127 const nlohmann::json& aSource,
128 const wxString& aDocProp )
129{
130 return ApplyProjectFilePatch( aTarget, aSource, aDocProp, DOC_KIND::PCB );
131}
132
133
134bool ApplyProjectFilePatches( const wxString& aOutputProPath,
135 const nlohmann::json& aSource,
136 const std::set<wxString>& aDocProps,
137 DOC_KIND aKind )
138{
139 if( aDocProps.empty() )
140 return true;
141
142 nlohmann::json target;
143
144 if( wxFileExists( aOutputProPath ) )
145 {
146 wxFFileInputStream fileStream( aOutputProPath );
147
148 if( !fileStream.IsOk() )
149 return false;
150
151 if( fileStream.GetLength() == 0 )
152 {
153 target = nlohmann::json::object();
154 }
155 else
156 {
157 try
158 {
159 // Parse the raw UTF-8 byte stream directly (matching
160 // JSON_SETTINGS::LoadFromFile). Round-tripping through
161 // wxString would re-encode via the C locale and corrupt
162 // non-ASCII content.
163 wxStdInputStream stdStream( fileStream );
164 target = nlohmann::json::parse( stdStream, nullptr, true, /* ignore_comments */ true );
165 }
166 catch( const std::exception& )
167 {
168 // Existing file is unparseable. Refuse to overwrite it with
169 // the patched source; the caller's full SaveProjectCopy
170 // fallback can take over.
171 return false;
172 }
173 }
174 }
175 else
176 {
177 // No pre-existing output file -- start from source and patch the
178 // requested fields onto it. The end result is identical to a full
179 // copy in this case, which is the right behaviour: we want the
180 // merge's chosen-side values in place.
181 target = aSource;
182 }
183
184 for( const wxString& docProp : aDocProps )
185 {
186 if( !ApplyProjectFilePatch( target, aSource, docProp, aKind ) )
187 return false;
188 }
189
190 // Write atomically so a crash or power loss mid-write can't corrupt the
191 // .kicad_pro (matches JSON_SETTINGS::SaveToFile). dump( 2 ) emits UTF-8;
192 // write its bytes verbatim so the on-disk content is byte-identical.
193 std::string payload = target.dump( 2 );
194
195 return KIPLATFORM::IO::AtomicWriteFile( aOutputProPath, payload.data(), payload.size() );
196}
197
198
199bool ApplyProjectFilePatches( const wxString& aOutputProPath,
200 const nlohmann::json& aSource,
201 const std::set<wxString>& aDocProps )
202{
203 return ApplyProjectFilePatches( aOutputProPath, aSource, aDocProps, DOC_KIND::PCB );
204}
205
206} // namespace KICAD_DIFF
const wxString DOC_PROP_ERC_SEVERITIES
const wxString DOC_PROP_NET_CLASSES
bool ApplyProjectFilePatch(nlohmann::json &aTarget, const nlohmann::json &aSource, const wxString &aDocProp, DOC_KIND aKind)
Copy the JSON sub-tree located at the pointer for aDocProp from aSource into aTarget.
const wxString DOC_PROP_DRAWING_SHEET
DOC_KIND
Document type a diff/merge entry point should route to, derived from a file path's extension.
const wxString DOC_PROP_DRC_SEVERITIES
std::optional< std::string > DocPropJsonPointer(const wxString &aDocProp, DOC_KIND aKind)
Return the JSON pointer (RFC 6901, slash-separated) under which aDocProp is persisted in a ....
bool ApplyProjectFilePatches(const wxString &aOutputProPath, const nlohmann::json &aSource, const std::set< wxString > &aDocProps, DOC_KIND aKind)
Higher-level orchestrator: load the existing aOutputProPath as JSON (or start from aSource if the fil...
bool AtomicWriteFile(const wxString &aTargetPath, const void *aData, size_t aSize, wxString *aError=nullptr)
Writes aData to aTargetPath via a sibling temp file, fsyncs the data and directory,...
std::vector< std::vector< std::string > > table