-
Notifications
You must be signed in to change notification settings - Fork 20
/
ClassTool.cs
359 lines (321 loc) · 13.1 KB
/
ClassTool.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using System.Xml.Serialization;
namespace GitForce
{
/// <summary>
/// Class describing a custom tool.
/// Also contains functions to Load, Save and Run custom tools.
/// </summary>
public class ClassTool : ICloneable
{
/// <summary>
/// Short name of the tool
/// </summary>
public string Name;
/// <summary>
/// Full path and name of the tool executable
/// </summary>
public string Cmd;
/// <summary>
/// Arguments to pass to the tool before it executes. Macros allowed.
/// </summary>
public string Args;
/// <summary>
/// Starting directory for a tool
/// </summary>
public string Dir;
/// <summary>
/// Longer description of the tool
/// </summary>
public string Desc;
/// <summary>
/// Add a tool to the context menu
/// </summary>
public bool IsAddToContextMenu;
/// <summary>
/// Tool is a console app
/// </summary>
public bool IsConsoleApp;
/// <summary>
/// Capture and write the output of a console tool into the status pane
/// </summary>
public bool IsWriteOutput;
/// <summary>
/// After a console tool exits, close its host command window
/// </summary>
public bool IsCloseWindowOnExit;
/// <summary>
/// After a tool exits, do a GitForce global refresh
/// </summary>
public bool IsRefresh;
/// <summary>
/// Before running a tool, prompt the user for extra arguments
/// </summary>
public bool IsPromptForArgs;
/// <summary>
/// Within the extra arguments dialog, show the file browse button
/// </summary>
public bool IsAddBrowse;
/// <summary>
/// Implements the clonable interface.
/// </summary>
public object Clone()
{
return MemberwiseClone();
}
/// <summary>
/// ToString override dumps all tool information for debug.
/// </summary>
public override string ToString()
{
return String.Format("Name: {0}\nCmd: {1}\nArgs: {2}\nDir: {3}\n",
Name, Cmd, Args, Dir);
}
/// <summary>
/// Runs a custom tool.
/// Returns a string with a tool output to be printed out.
/// This string can be empty, in which case nothing should be printed.
/// </summary>
public string Run(List<string> files)
{
App.PrintLogMessage(ToString(), MessageType.Command);
string stdout = string.Empty;
string args = DeMacroise(Args, files);
// Add custom arguments if the checkbox to Prompt for Arguments was checked
if (IsPromptForArgs)
{
// Description is used as a question for the arguments, shown in the window title bar
string desc = Name;
if (!string.IsNullOrEmpty(Desc)) desc += ": " + Desc;
FormCustomToolArgs formCustomToolArgs = new FormCustomToolArgs(desc, args, IsAddBrowse);
if (formCustomToolArgs.ShowDialog() == DialogResult.Cancel)
return string.Empty;
args = formCustomToolArgs.GetArgs();
}
App.StatusBusy(true);
// Prepare the process to be run
Process proc = new Process();
proc.StartInfo.FileName = "\"" + Cmd + "\"";
proc.StartInfo.Arguments = args;
proc.StartInfo.WorkingDirectory = DeMacroise(Dir, new List<string>());
proc.StartInfo.UseShellExecute = false;
try
{
// Run the custom tool in two ways (console app and GUI app)
if (IsConsoleApp)
{
// Start a console process
proc.StartInfo.CreateNoWindow = false;
// If we have to keep the window open (CMD/SHELL) after exit,
// we start the command line app in a different way, using a
// shell command (in which case we cannot redirect the stdout)
if (IsCloseWindowOnExit)
{
App.MainForm.SetTitle("Waiting for " + Cmd + " to finish...");
// Redirect standard output to our status pane if requested
if (IsWriteOutput)
{
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.OutputDataReceived += ProcOutputDataReceived;
proc.ErrorDataReceived += ProcErrorDataReceived;
proc.Start();
proc.BeginOutputReadLine();
proc.WaitForExit();
}
else
{
proc.Start();
proc.WaitForExit();
}
}
else
{
// We need to keep the CMD/SHELL window open, so start the process using
// the CMD/SHELL as the root process and pass it our command to execute
proc.StartInfo.Arguments = string.Format("{0} {1} {2}",
ClassUtils.GetShellExecFlags(), proc.StartInfo.FileName, proc.StartInfo.Arguments);
proc.StartInfo.FileName = ClassUtils.GetShellExecCmd();
App.PrintLogMessage(proc.StartInfo.Arguments, MessageType.Command);
proc.Start();
}
}
else
{
// Start a GUI process
proc.StartInfo.CreateNoWindow = true;
// We can start the process and wait for it to finish only if we need to
// refresh the app after the process has exited.
proc.Start();
}
if (IsRefresh)
{
App.MainForm.SetTitle("Waiting for " + Cmd + " to finish...");
proc.WaitForExit();
App.DoRefresh();
}
}
catch (Exception ex)
{
App.PrintStatusMessage(ex.Message, MessageType.Error);
MessageBox.Show(ex.Message, "Error executing custom tool", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
proc.Close();
App.StatusBusy(false);
return stdout;
}
/// <summary>
/// Callback that handles process printing to stdout
/// Print to the application status pane.
/// </summary>
private void ProcOutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (String.IsNullOrEmpty(e.Data)) return;
App.PrintStatusMessage(e.Data + Environment.NewLine, MessageType.General);
}
/// <summary>
/// Callback that handles process printing to stderr
/// Print to the application status pane.
/// </summary>
private void ProcErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (String.IsNullOrEmpty(e.Data)) return;
App.PrintStatusMessage(e.Data + Environment.NewLine, MessageType.Error);
}
/// <summary>
/// Applies a set of macro resolutions to the input string
/// </summary>
private string DeMacroise(string s, List<string> files)
{
// Without the current repo, we cannot have reasonable macro expansions
s = s.Replace("%r", App.Repos.Current==null ? "" : App.Repos.Current.Path);
s = s.Replace("%u", App.Repos.Current==null ? "" : App.Repos.Current.UserName);
s = s.Replace("%e", App.Repos.Current==null ? "" : App.Repos.Current.UserEmail);
s = s.Replace("%b", App.Repos.Current==null ? "" : App.Repos.Current.Branches.Current);
// Separate given list into list of files and list of directories
List<string> F = new List<string>();
List<string> D = new List<string>();
foreach (string f in files)
{
if (Directory.Exists(f))
// For directories, remove trailing slash
D.Add(f.TrimEnd(new[] {'\\', '/'}));
else
F.Add(f);
}
// Single file and single directory
string sf = F.Count > 0 ? F[0] : "";
string sd = D.Count > 0 ? D[0] : "";
s = s.Replace("%f", sf);
s = s.Replace("%d", sd);
s = s.Replace("%F", string.Join(" ", F.ToArray()));
s = s.Replace("%D", string.Join(" ", D.ToArray()));
return s;
}
}
/// <summary>
/// Class describing our set of custom tools
/// </summary>
public class ClassCustomTools
{
/// <summary>
/// List of tools
/// </summary>
public readonly List<ClassTool> Tools = new List<ClassTool>();
/// <summary>
/// Load a set of tools from a given file into the current tool-set.
/// Returns a new class structure containing all the tools if the tools loaded correctly.
/// If load failed, return empty class and print the error message to a main pane.
/// </summary>
public static ClassCustomTools Load(string name)
{
App.PrintStatusMessage("Loading custom tools: " + name, MessageType.General);
ClassCustomTools ct = new ClassCustomTools();
try
{
XmlSerializer deserializer = new XmlSerializer(typeof(ClassCustomTools));
using (TextReader textReader = new StreamReader(name))
{
ct = (ClassCustomTools)deserializer.Deserialize(textReader);
}
}
catch (Exception ex)
{
// It is OK not to find custom tools file (for example, the app is being run the very first time)
// All other errors are being reported
if (!(ex is FileNotFoundException))
App.PrintStatusMessage("Error loading custom tools: " + ex.Message, MessageType.Error);
}
return ct;
}
/// <summary>
/// Save current set of tools to a given file.
/// Returns true if save successful.
/// If save failed, return false and print the error message to a main pane.
/// </summary>
public bool Save(string name)
{
try
{
XmlSerializer serializer = new XmlSerializer(typeof(ClassCustomTools));
using (TextWriter textWriter = new StreamWriter(name))
{
serializer.Serialize(textWriter, this);
}
}
catch (Exception ex)
{
App.PrintStatusMessage("Error saving custom tools: " + ex.Message, MessageType.Error);
return false;
}
return true;
}
/// <summary>
/// Implements a deep copy of the whole class.
/// </summary>
public ClassCustomTools Copy()
{
ClassCustomTools ct = new ClassCustomTools();
foreach (var classTool in Tools)
ct.Tools.Add((ClassTool)classTool.Clone());
return ct;
}
#region Utility function to find local tools
/// <summary>
/// Utility function to find few local tools: experimental
/// </summary>
public static List<ClassTool> FindLocalTools()
{
List<ClassTool> tools = new List<ClassTool>();
try
{
// If we have msysgit, we should also have few other bundled tools
string gitpath = Properties.Settings.Default.GitPath;
if (File.Exists(gitpath))
{
string gitRoot = Path.GetDirectoryName(gitpath);
// ======= Find and add Git Bash =======
string GitBash = Path.Combine(gitRoot, "sh.exe");
if (File.Exists(GitBash))
{
ClassTool newTool = new ClassTool();
newTool.Name = "Git Bash";
newTool.Cmd = GitBash;
newTool.Dir = "%r";
newTool.Args = "--login -i";
newTool.IsConsoleApp = true;
newTool.IsAddToContextMenu = true;
tools.Add(newTool);
}
}
}
catch { } // Never mind.
return tools;
}
#endregion
}
}