E-MailRelay

gbatchfile.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gbatchfile.cpp
19///
20
21#include "gdef.h"
22#include "gbatchfile.h"
23#include "gcodepage.h"
24#include "gfile.h"
25#include "garg.h"
26#include "gstr.h"
27#include "glog.h"
28#include "gassert.h"
29#include <stdexcept>
30#include <fstream>
31
32 G::BatchFile::BatchFile( const Path & path )
33{
34 init( path ) ;
35}
36
37 G::BatchFile::BatchFile( const Path & path , std::nothrow_t )
38{
39 try
40 {
41 init( path ) ;
42 }
43 catch( Error & )
44 {
45 clear() ;
46 }
47}
48
49 void G::BatchFile::init( const Path & path )
50{
51 std::ifstream stream ;
52 File::open( stream , path , File::Text() ) ;
53 if( !stream.good() )
54 throw Error( "cannot open batch file" , path.str() ) ;
55 m_raw_line = readFrom( stream , path.str() ) ;
56 auto parse_result = parse( m_raw_line ) ;
57 if( !parse_result.error.empty() )
58 throw Error( parse_result.error , path.str() ) ;
59 m_name = parse_result.name ;
60 m_line = parse_result.line ;
61 m_args = split( m_line ) ;
62}
63
64 void G::BatchFile::clear()
65{
66 m_raw_line.clear() ;
67 m_name.clear() ;
68 m_line.clear() ;
69 m_args.clear() ;
70}
71
72 bool G::BatchFile::empty() const
73{
74 return m_line.empty() ;
75}
76
77 bool G::BatchFile::ignorable( const std::string & trimmed_line )
78{
79 return
80 trimmed_line.empty() ||
81 Str::lower(trimmed_line+" ").find("@echo ") == 0U ||
82 Str::lower(trimmed_line+" ").find("rem ") == 0U ;
83}
84
85 bool G::BatchFile::relevant( const std::string & trimmed_line )
86{
87 return !ignorable( trimmed_line ) ;
88}
89
90std::string G::BatchFile::readFrom( std::istream & stream , const std::string & stream_name , bool do_throw )
91{
92 std::string line ;
93 while( stream.good() )
94 {
95 std::string s = Str::readLineFrom( stream ) ;
96 if( !stream ) break ;
97 Str::trim( s , Str::ws() ) ;
98 Str::replaceAll( s , "\t" , " " ) ;
99 s = Str::unique( s , ' ' ) ;
100 if( relevant(s) )
101 {
102 if( line.empty() )
103 line = s ;
104 else if( do_throw )
105 throw Error( "too many lines in batch file" , stream_name ) ;
106 else
107 return {} ;
108 }
109 }
110
111 if( line.empty() )
112 {
113 if( do_throw )
114 throw Error( "batch file is empty" , stream_name ) ;
115 return {} ;
116 }
117
118 return line ;
119}
120
121G::BatchFile::ParseResult G::BatchFile::parse( const std::string & line_in )
122{
123 // strip off any "start" prefix -- allow the "start" to have a quoted window
124 // title but dont expect any "start" options such as "/min"
125
126 ParseResult result ;
127 std::string line = line_in ;
128 if( !line.empty() )
129 {
130 using size_type = std::string::size_type ;
131 size_type const npos = std::string::npos ;
132 std::string ws = sv_to_string( Str::ws() ) ;
133
134 std::string start = "start " ;
135 size_type start_pos = Str::lower(line).find(start) ;
136 size_type command_pos = start_pos == npos ? 0U : line.find_first_not_of( ws , start_pos+start.size() ) ;
137
138 bool named = start_pos != npos && line.at(command_pos) == '"' ;
139 if( named )
140 {
141 std::size_t name_start_pos = command_pos ;
142 std::size_t name_end_pos = line.find( '\"' , name_start_pos+1U ) ;
143 if( name_end_pos == npos )
144 return {{},{},"mismatched quotes in batch file"} ;
145 if( (name_end_pos+2U) >= line.size() || line.at(name_end_pos+1U) != ' ' )
146 return {{},{},"invalid window name in batch file"} ;
147
148 result.name = line.substr( name_start_pos+1U , name_end_pos-(name_start_pos+1U) ) ;
149 dequote( result.name ) ;
150 Str::trim( result.name , Str::ws() ) ;
151
152 command_pos = line.find_first_not_of( ws , name_end_pos+2U ) ;
153 }
154
155 if( command_pos != npos )
156 line.erase( 0U , command_pos ) ;
157 }
158
159 // percent characters are doubled up in batch files so un-double them here
160 Str::replaceAll( line , "%%" , "%" ) ;
161
162 result.line = CodePage::fromCodePageOem( line ) ; // to UTF-8
163 return result ;
164}
165
166std::string G::BatchFile::line() const
167{
168 return m_line ;
169}
170
171std::string G::BatchFile::name() const
172{
173 return m_name ;
174}
175
176 const G::StringArray & G::BatchFile::args() const
177{
178 return m_args ;
179}
180
181std::size_t G::BatchFile::lineArgsPos() const
182{
183 constexpr char qq = '\"' ;
184 std::size_t i = 0U ;
185 for( bool in_quote = false ; i < m_line.size() ; i++ )
186 {
187 char c = m_line[i] ;
188 if( c == qq && !in_quote )
189 in_quote = true ;
190 else if( c == qq )
191 in_quote = false ;
192 else if( Str::ws().find(c) != std::string::npos && !in_quote )
193 break ;
194 }
195 return i ;
196}
197
198 void G::BatchFile::dequote( std::string & s )
199{
200 if( s.size() >= 2U && s.find('\"') == 0U && (s.rfind('\"')+1U) == s.size() )
201 s = s.substr( 1U , s.size()-2U ) ;
202}
203
204 void G::BatchFile::write( const Path & path , const StringArray & args , const std::string & name_in , bool make_backup )
205{
206 G_ASSERT( !args.empty() ) ;
207 if( args.empty() )
208 throw Error( "invalid contents for startup batch file" ) ; // need at least the executable
209
210 std::string name = name_in ;
211 if( name.empty() )
212 {
213 name = args.at(0U) ;
214 dequote( name ) ;
215 name = Path(name).withoutExtension().basename() ;
216 }
217
218 std::string start_line ;
219 {
220 std::ostringstream ss ;
221 ss << "start \"" << CodePage::toCodePageOem(name) << "\"" ;
222 for( const auto & arg : args )
223 {
224 ss << " " << percents(quote(CodePage::toCodePageOem(arg))) ;
225 }
226 start_line = ss.str() ;
227 }
228
229 if( make_backup )
230 {
231 BatchFile on_disk( path , std::nothrow ) ;
232 if( start_line != on_disk.m_raw_line )
233 G::File::backup( path , std::nothrow ) ;
234 }
235
236 std::ofstream stream ;
237 File::open( stream , path ) ;
238 if( !stream.good() )
239 throw Error( "cannot create batch file" , path.str() ) ;
240
241 stream << start_line << "\r\n" ;
242 stream.close() ;
243 if( stream.fail() )
244 throw Error( "cannot write batch file" , path.str() ) ;
245}
246
247 G::StringArray G::BatchFile::split( const std::string & line )
248{
249 // get G::Arg to deal with the quotes
250 return Arg(line).array() ;
251}
252
253std::string G::BatchFile::percents( const std::string & s )
254{
255 std::string result( s ) ;
256 Str::replaceAll( result , "%" , "%%" ) ;
257 return result ;
258}
259
260std::string G::BatchFile::quote( const std::string & s )
261{
262 return
263 s.find('\"') == std::string::npos && s.find_first_of(" \t") != std::string::npos ?
264 "\"" + s + "\"" : s ;
265}
266
G::Arg
A class which holds a represention of the argc/argv command line array, and supports simple command-l...
Definition: garg.h:50
G::Arg::array
StringArray array(unsigned int shift=0U) const
Returns the arguments as a string array, with an optional shift.
Definition: garg.cpp:88
G::BatchFile
A class for reading and writing windows-style startup batch files containing a single command-line,...
Definition: gbatchfile.h:53
G::BatchFile::args
const StringArray & args() const
Returns the startup command-line broken up into de-quoted pieces.
Definition: gbatchfile.cpp:176
G::BatchFile::empty
bool empty() const
Returns true if line() is empty.
Definition: gbatchfile.cpp:72
G::BatchFile::lineArgsPos
std::size_t lineArgsPos() const
Returns the position in line() where the arguments start.
Definition: gbatchfile.cpp:181
G::BatchFile::name
std::string name() const
Returns the "start" window name, if any.
Definition: gbatchfile.cpp:171
G::BatchFile::BatchFile
BatchFile(const Path &)
Constructor that reads from a file.
Definition: gbatchfile.cpp:32
G::BatchFile::line
std::string line() const
Returns the main command-line from within the batchfile, with normalised spaces, without any "start" ...
Definition: gbatchfile.cpp:166
G::BatchFile::write
static void write(const Path &new_batch_file, const StringArray &args, const std::string &start_window_name={}, bool do_backup=false)
Writes a startup batch file, including a "start" prefix.
Definition: gbatchfile.cpp:204
G::File::Text
An overload discriminator for G::File::open().
Definition: gfile.h:64
G::File::open
static void open(std::ofstream &, const Path &)
Calls open() on the given output file stream.
Definition: gfile_unix.cpp:56
G::File::backup
static Path backup(const Path &from, std::nothrow_t)
Creates a backup copy of the given file in the same directory and with a lightly-mangled filename.
Definition: gfile.cpp:322
G::Path
A Path object represents a file system path.
Definition: gpath.h:82
G::Path::basename
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:339
G::Path::withoutExtension
Path withoutExtension() const
Returns a path without the basename extension, if any.
Definition: gpath.cpp:357
G::Path::str
std::string str() const
Returns the path string.
Definition: gpath.h:243
G::Str::lower
static std::string lower(std::string_view)
Returns a copy of 's' in which all seven-bit upper-case characters have been replaced by lower-case c...
Definition: gstr.cpp:824
G::Str::trim
static std::string & trim(std::string &s, std::string_view ws)
Trims both ends of s, taking off any of the 'ws' characters.
Definition: gstr.cpp:338
G::Str::unique
static std::string unique(const std::string &s, char c, char r)
Returns a string with repeated 'c' characters replaced by one 'r' character.
Definition: gstr.cpp:1467
G::Str::readLineFrom
static std::string readLineFrom(std::istream &stream, std::string_view eol={})
Reads a line from the stream using the given line terminator.
Definition: gstr.cpp:951
G::Str::replaceAll
static unsigned int replaceAll(std::string &s, std::string_view from, std::string_view to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
Definition: gstr.cpp:247
G::Str::ws
static std::string_view ws() noexcept
Returns a string of standard whitespace characters.
Definition: gstr.cpp:1265
garg.h
gassert.h
gbatchfile.h
gcodepage.h
gfile.h
glog.h
gstr.h
G::CodePage::toCodePageOem
std::string toCodePageOem(std::string_view)
Converts from UTF-8 to the active OEM codepage (see GetOEMCP(), 850 on unix).
Definition: gcodepage.cpp:203
G::CodePage::fromCodePageOem
std::string fromCodePageOem(std::string_view)
Converts from the active OEM codepage (see GetOEMCP(), 850 on unix) to UTF-8.
Definition: gcodepage.cpp:246
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30