PageRenderTime 53ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/nconsoler/NConsoler.cs

http://github.com/techtalk/SpecFlow
C# | 925 lines | 783 code | 82 blank | 60 comment | 120 complexity | 628567ddccbb2ffc417ecee6146284ef MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0
  1. //
  2. // NConsoler 0.9.3
  3. // http://nconsoler.csharpus.com
  4. //
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Reflection;
  8. using System.Diagnostics;
  9. namespace NConsoler
  10. {
  11. /// <summary>
  12. /// Entry point for NConsoler applications
  13. /// </summary>
  14. public sealed class Consolery
  15. {
  16. /// <summary>
  17. /// Runs an appropriate Action method.
  18. /// Uses the class this call lives in as target type and command line arguments from Environment
  19. /// </summary>
  20. public static void Run()
  21. {
  22. Type declaringType = new StackTrace().GetFrame(1).GetMethod().DeclaringType;
  23. string[] args = new string[Environment.GetCommandLineArgs().Length - 1];
  24. new List<string>(Environment.GetCommandLineArgs()).CopyTo(1, args, 0, Environment.GetCommandLineArgs().Length - 1);
  25. Run(declaringType, args);
  26. }
  27. /// <summary>
  28. /// Runs an appropriate Action method
  29. /// </summary>
  30. /// <param name="targetType">Type where to search for Action methods</param>
  31. /// <param name="args">Arguments that will be converted to Action method arguments</param>
  32. public static void Run(Type targetType, string[] args)
  33. {
  34. Run(targetType, args, new ConsoleMessenger());
  35. }
  36. /// <summary>
  37. /// Runs an appropriate Action method
  38. /// </summary>
  39. /// <param name="targetType">Type where to search for Action methods</param>
  40. /// <param name="args">Arguments that will be converted to Action method arguments</param>
  41. /// <param name="messenger">Uses for writing messages instead of Console class methods</param>
  42. public static void Run(Type targetType, string[] args, IMessenger messenger)
  43. {
  44. try
  45. {
  46. new Consolery(targetType, args, messenger).RunAction();
  47. }
  48. catch (NConsolerException e)
  49. {
  50. messenger.Write(e.Message);
  51. }
  52. }
  53. /// <summary>
  54. /// Validates specified type and throws NConsolerException if an error
  55. /// </summary>
  56. /// <param name="targetType">Type where to search for Action methods</param>
  57. public static void Validate(Type targetType)
  58. {
  59. new Consolery(targetType, new string[] {}, new ConsoleMessenger()).ValidateMetadata();
  60. }
  61. private readonly Type _targetType;
  62. private readonly string[] _args;
  63. private readonly List<MethodInfo> _actionMethods = new List<MethodInfo>();
  64. private readonly IMessenger _messenger;
  65. private Consolery(Type targetType, string[] args, IMessenger messenger)
  66. {
  67. #region Parameter Validation
  68. if (targetType == null)
  69. {
  70. throw new ArgumentNullException("targetType");
  71. }
  72. if (args == null)
  73. {
  74. throw new ArgumentNullException("args");
  75. }
  76. if (messenger == null)
  77. {
  78. throw new ArgumentNullException("messenger");
  79. }
  80. #endregion
  81. _targetType = targetType;
  82. _args = args;
  83. _messenger = messenger;
  84. MethodInfo[] methods = _targetType.GetMethods(BindingFlags.Public | BindingFlags.Static);
  85. foreach (MethodInfo method in methods)
  86. {
  87. object[] attributes = method.GetCustomAttributes(false);
  88. foreach (object attribute in attributes)
  89. {
  90. if (attribute is ActionAttribute)
  91. {
  92. _actionMethods.Add(method);
  93. break;
  94. }
  95. }
  96. }
  97. }
  98. static object ConvertValue(string value, Type argumentType)
  99. {
  100. if (argumentType == typeof(int))
  101. {
  102. try
  103. {
  104. return Convert.ToInt32(value);
  105. }
  106. catch (FormatException)
  107. {
  108. throw new NConsolerException("Could not convert \"{0}\" to integer", value);
  109. }
  110. catch (OverflowException)
  111. {
  112. throw new NConsolerException("Value \"{0}\" is too big or too small", value);
  113. }
  114. }
  115. if (argumentType == typeof(string))
  116. {
  117. return value;
  118. }
  119. if (argumentType == typeof(bool))
  120. {
  121. try
  122. {
  123. return Convert.ToBoolean(value);
  124. }
  125. catch (FormatException)
  126. {
  127. throw new NConsolerException("Could not convert \"{0}\" to boolean", value);
  128. }
  129. }
  130. if (argumentType == typeof(string[]))
  131. {
  132. return value.Split('+', ';');
  133. }
  134. if (argumentType == typeof(int[]))
  135. {
  136. string[] values = value.Split('+');
  137. int[] valuesArray = new int[values.Length];
  138. for (int i = 0; i < values.Length; i++)
  139. {
  140. valuesArray[i] = (int)ConvertValue(values[i], typeof(int));
  141. }
  142. return valuesArray;
  143. }
  144. if (argumentType == typeof(DateTime))
  145. {
  146. return ConvertToDateTime(value);
  147. }
  148. throw new NConsolerException("Unknown type is used in your method {0}", argumentType.FullName);
  149. }
  150. private static DateTime ConvertToDateTime(string parameter)
  151. {
  152. string[] parts = parameter.Split('-');
  153. if (parts.Length != 3)
  154. {
  155. throw new NConsolerException("Could not convert {0} to Date", parameter);
  156. }
  157. int day = (int)ConvertValue(parts[0], typeof(int));
  158. int month = (int)ConvertValue(parts[1], typeof(int));
  159. int year = (int)ConvertValue(parts[2], typeof(int));
  160. try
  161. {
  162. return new DateTime(year, month, day);
  163. }
  164. catch (ArgumentException)
  165. {
  166. throw new NConsolerException("Could not convert {0} to Date", parameter);
  167. }
  168. }
  169. private static bool CanBeConvertedToDate(string parameter)
  170. {
  171. try
  172. {
  173. ConvertToDateTime(parameter);
  174. return true;
  175. }
  176. catch(NConsolerException)
  177. {
  178. return false;
  179. }
  180. }
  181. private bool SingleActionWithOnlyOptionalParametersSpecified() {
  182. if (IsMulticommand) return false;
  183. MethodInfo method = _actionMethods[0];
  184. return OnlyOptionalParametersSpecified(method);
  185. }
  186. private static bool OnlyOptionalParametersSpecified(MethodBase method)
  187. {
  188. foreach (ParameterInfo parameter in method.GetParameters())
  189. {
  190. if (IsRequired(parameter))
  191. {
  192. return false;
  193. }
  194. }
  195. return true;
  196. }
  197. private void RunAction()
  198. {
  199. ValidateMetadata();
  200. if (IsHelpRequested())
  201. {
  202. PrintUsage();
  203. return;
  204. }
  205. MethodInfo currentMethod = GetCurrentMethod();
  206. if (currentMethod == null)
  207. {
  208. PrintUsage();
  209. throw new NConsolerException("Unknown subcommand \"{0}\"", _args[0]);
  210. }
  211. ValidateInput(currentMethod);
  212. InvokeMethod(currentMethod);
  213. }
  214. private struct ParameterData
  215. {
  216. public readonly int position;
  217. public readonly Type type;
  218. public ParameterData(int position, Type type)
  219. {
  220. this.position = position;
  221. this.type = type;
  222. }
  223. }
  224. private static bool IsRequired(ICustomAttributeProvider info)
  225. {
  226. object[] attributes = info.GetCustomAttributes(typeof(ParameterAttribute), false);
  227. return attributes.Length == 0 || attributes[0].GetType() == typeof(RequiredAttribute);
  228. }
  229. private static bool IsOptional(ICustomAttributeProvider info)
  230. {
  231. return !IsRequired(info);
  232. }
  233. private static OptionalAttribute GetOptional(ICustomAttributeProvider info)
  234. {
  235. object[] attributes = info.GetCustomAttributes(typeof(OptionalAttribute), false);
  236. return (OptionalAttribute)attributes[0];
  237. }
  238. private bool IsMulticommand
  239. {
  240. get
  241. {
  242. return _actionMethods.Count > 1;
  243. }
  244. }
  245. private bool IsHelpRequested()
  246. {
  247. return (_args.Length == 0 && !SingleActionWithOnlyOptionalParametersSpecified())
  248. || (_args.Length > 0 && (_args[0] == "/?"
  249. || _args[0] == "/help"
  250. || _args[0] == "/h"
  251. || _args[0] == "help"));
  252. }
  253. private void InvokeMethod(MethodInfo method)
  254. {
  255. try
  256. {
  257. method.Invoke(null, BuildParameterArray(method));
  258. }
  259. catch (TargetInvocationException e)
  260. {
  261. if (e.InnerException != null)
  262. {
  263. throw new NConsolerException(e.InnerException.Message, e);
  264. }
  265. throw;
  266. }
  267. }
  268. private object[] BuildParameterArray(MethodInfo method)
  269. {
  270. int argumentIndex = IsMulticommand ? 1 : 0;
  271. List<object> parameterValues = new List<object>();
  272. Dictionary<string, ParameterData> aliases = new Dictionary<string, ParameterData>();
  273. foreach (ParameterInfo info in method.GetParameters())
  274. {
  275. if (IsRequired(info))
  276. {
  277. parameterValues.Add(ConvertValue(_args[argumentIndex], info.ParameterType));
  278. }
  279. else
  280. {
  281. OptionalAttribute optional = GetOptional(info);
  282. foreach (string altName in optional.AltNames)
  283. {
  284. aliases.Add(altName.ToLower(),
  285. new ParameterData(parameterValues.Count, info.ParameterType));
  286. }
  287. aliases.Add(info.Name.ToLower(),
  288. new ParameterData(parameterValues.Count, info.ParameterType));
  289. parameterValues.Add(optional.Default);
  290. }
  291. argumentIndex++;
  292. }
  293. foreach (string optionalParameter in OptionalParameters(method))
  294. {
  295. string name = ParameterName(optionalParameter);
  296. string value = ParameterValue(optionalParameter);
  297. parameterValues[aliases[name].position] = ConvertValue(value, aliases[name].type);
  298. }
  299. return parameterValues.ToArray();
  300. }
  301. private IEnumerable<string> OptionalParameters(MethodInfo method)
  302. {
  303. int firstOptionalParameterIndex = RequiredParameterCount(method);
  304. if (IsMulticommand)
  305. {
  306. firstOptionalParameterIndex++;
  307. }
  308. for (int i = firstOptionalParameterIndex; i < _args.Length; i++)
  309. {
  310. yield return _args[i];
  311. }
  312. }
  313. private static int RequiredParameterCount(MethodInfo method)
  314. {
  315. int requiredParameterCount = 0;
  316. foreach (ParameterInfo parameter in method.GetParameters())
  317. {
  318. if (IsRequired(parameter))
  319. {
  320. requiredParameterCount++;
  321. }
  322. }
  323. return requiredParameterCount;
  324. }
  325. private MethodInfo GetCurrentMethod()
  326. {
  327. if (!IsMulticommand)
  328. {
  329. return _actionMethods[0];
  330. }
  331. return GetMethodByName(_args[0].ToLower());
  332. }
  333. private MethodInfo GetMethodByName(string name)
  334. {
  335. foreach (MethodInfo method in _actionMethods)
  336. {
  337. if (method.Name.ToLower() == name)
  338. {
  339. return method;
  340. }
  341. }
  342. return null;
  343. }
  344. private void PrintUsage(MethodInfo method)
  345. {
  346. PrintMethodDescription(method);
  347. Dictionary<string, string> parameters = GetParametersDescriptions(method);
  348. PrintUsageExample(method, parameters);
  349. PrintParametersDescriptions(parameters);
  350. }
  351. private void PrintUsageExample(MethodInfo method, IDictionary<string, string> parameterList)
  352. {
  353. string subcommand = IsMulticommand ? method.Name.ToLower() + " " : String.Empty;
  354. string parameters = String.Join(" ", new List<string>(parameterList.Keys).ToArray());
  355. _messenger.Write("usage: " + ProgramName() + " " + subcommand + parameters);
  356. }
  357. private void PrintMethodDescription(MethodInfo method)
  358. {
  359. string description = GetMethodDescription(method);
  360. if (description == String.Empty) return;
  361. _messenger.Write(description);
  362. }
  363. private static string GetMethodDescription(MethodInfo method)
  364. {
  365. object[] attributes = method.GetCustomAttributes(true);
  366. foreach (object attribute in attributes)
  367. {
  368. if (attribute is ActionAttribute)
  369. {
  370. return ((ActionAttribute)attribute).Description;
  371. }
  372. }
  373. throw new NConsolerException("Method is not marked with an Action attribute");
  374. }
  375. private static Dictionary<string, string> GetParametersDescriptions(MethodInfo method)
  376. {
  377. Dictionary<string, string> parameters = new Dictionary<string, string>();
  378. foreach (ParameterInfo parameter in method.GetParameters())
  379. {
  380. object[] parameterAttributes =
  381. parameter.GetCustomAttributes(typeof(ParameterAttribute), false);
  382. if (parameterAttributes.Length > 0)
  383. {
  384. string name = GetDisplayName(parameter);
  385. ParameterAttribute attribute = (ParameterAttribute)parameterAttributes[0];
  386. parameters.Add(name, attribute.Description);
  387. }
  388. else
  389. {
  390. parameters.Add(parameter.Name, String.Empty);
  391. }
  392. }
  393. return parameters;
  394. }
  395. private void PrintParametersDescriptions(IEnumerable<KeyValuePair<string, string>> parameters)
  396. {
  397. int maxParameterNameLength = MaxKeyLength(parameters);
  398. foreach (KeyValuePair<string, string> pair in parameters)
  399. {
  400. if (pair.Value != String.Empty)
  401. {
  402. int difference = maxParameterNameLength - pair.Key.Length + 2;
  403. _messenger.Write(" " + pair.Key + new String(' ', difference) + pair.Value);
  404. }
  405. }
  406. }
  407. private static int MaxKeyLength(IEnumerable<KeyValuePair<string, string>> parameters)
  408. {
  409. int maxLength = 0;
  410. foreach (KeyValuePair<string, string> pair in parameters)
  411. {
  412. if (pair.Key.Length > maxLength)
  413. {
  414. maxLength = pair.Key.Length;
  415. }
  416. }
  417. return maxLength;
  418. }
  419. private string ProgramName()
  420. {
  421. Assembly entryAssembly = Assembly.GetEntryAssembly();
  422. if (entryAssembly == null)
  423. {
  424. return _targetType.Name.ToLower();
  425. }
  426. return new AssemblyName(entryAssembly.FullName).Name;
  427. }
  428. private void PrintUsage()
  429. {
  430. if (IsMulticommand && !IsSubcommandHelpRequested())
  431. {
  432. PrintGeneralMulticommandUsage();
  433. }
  434. else if (IsMulticommand && IsSubcommandHelpRequested())
  435. {
  436. PrintSubcommandUsage();
  437. }
  438. else
  439. {
  440. PrintUsage(_actionMethods[0]);
  441. }
  442. }
  443. private void PrintSubcommandUsage()
  444. {
  445. MethodInfo method = GetMethodByName(_args[1].ToLower());
  446. if (method == null)
  447. {
  448. PrintGeneralMulticommandUsage();
  449. throw new NConsolerException("Unknown subcommand \"{0}\"", _args[0].ToLower());
  450. }
  451. PrintUsage(method);
  452. }
  453. private bool IsSubcommandHelpRequested()
  454. {
  455. return _args.Length > 0
  456. && _args[0].ToLower() == "help"
  457. && _args.Length == 2;
  458. }
  459. private void PrintGeneralMulticommandUsage()
  460. {
  461. _messenger.Write(
  462. String.Format("usage: {0} <subcommand> [args]", ProgramName()));
  463. _messenger.Write(
  464. String.Format("Type '{0} help <subcommand>' for help on a specific subcommand.", ProgramName()));
  465. _messenger.Write(String.Empty);
  466. _messenger.Write("Available subcommands:");
  467. foreach (MethodInfo method in _actionMethods)
  468. {
  469. _messenger.Write(method.Name.ToLower() + " - " + GetMethodDescription(method));
  470. }
  471. }
  472. private static string GetDisplayName(ParameterInfo parameter)
  473. {
  474. if (IsRequired(parameter))
  475. {
  476. return parameter.Name;
  477. }
  478. OptionalAttribute optional = GetOptional(parameter);
  479. string parameterName =
  480. (optional.AltNames.Length > 0) ? optional.AltNames[0] : parameter.Name;
  481. if (parameter.ParameterType != typeof(bool))
  482. {
  483. parameterName += ":" + ValueDescription(parameter.ParameterType);
  484. }
  485. return "[/" + parameterName + "]";
  486. }
  487. private static string ValueDescription(Type type)
  488. {
  489. if (type == typeof(int))
  490. {
  491. return "number";
  492. }
  493. if (type == typeof(string))
  494. {
  495. return "value";
  496. }
  497. if (type == typeof(int[]))
  498. {
  499. return "number[+number]";
  500. }
  501. if (type == typeof(string[]))
  502. {
  503. return "value[+value]";
  504. }
  505. if (type == typeof(DateTime))
  506. {
  507. return "dd-mm-yyyy";
  508. }
  509. throw new ArgumentOutOfRangeException(String.Format("Type {0} is unknown", type.Name));
  510. }
  511. #region Validation
  512. private void ValidateInput(MethodInfo method)
  513. {
  514. CheckAllRequiredParametersAreSet(method);
  515. CheckOptionalParametersAreNotDuplicated(method);
  516. CheckUnknownParametersAreNotPassed(method);
  517. }
  518. private void CheckAllRequiredParametersAreSet(MethodInfo method)
  519. {
  520. int minimumArgsLengh = RequiredParameterCount(method);
  521. if (IsMulticommand)
  522. {
  523. minimumArgsLengh++;
  524. }
  525. if (_args.Length < minimumArgsLengh)
  526. {
  527. throw new NConsolerException("Not all required parameters are set");
  528. }
  529. }
  530. private static string ParameterName(string parameter)
  531. {
  532. if (parameter.StartsWith("/-"))
  533. {
  534. return parameter.Substring(2).ToLower();
  535. }
  536. if (parameter.Contains(":"))
  537. {
  538. return parameter.Substring(1, parameter.IndexOf(":") - 1).ToLower();
  539. }
  540. return parameter.Substring(1).ToLower();
  541. }
  542. private static string ParameterValue(string parameter)
  543. {
  544. if (parameter.StartsWith("/-"))
  545. {
  546. return "false";
  547. }
  548. if (parameter.Contains(":"))
  549. {
  550. return parameter.Substring(parameter.IndexOf(":") + 1);
  551. }
  552. return "true";
  553. }
  554. private void CheckOptionalParametersAreNotDuplicated(MethodInfo method)
  555. {
  556. List<string> passedParameters = new List<string>();
  557. foreach (string optionalParameter in OptionalParameters(method))
  558. {
  559. if (!optionalParameter.StartsWith("/"))
  560. {
  561. throw new NConsolerException("Unknown parameter {0}", optionalParameter);
  562. }
  563. string name = ParameterName(optionalParameter);
  564. if (passedParameters.Contains(name))
  565. {
  566. throw new NConsolerException("Parameter with name {0} passed two times", name);
  567. }
  568. passedParameters.Add(name);
  569. }
  570. }
  571. private void CheckUnknownParametersAreNotPassed(MethodInfo method)
  572. {
  573. List<string> parameterNames = new List<string>();
  574. foreach (ParameterInfo parameter in method.GetParameters())
  575. {
  576. if (IsRequired(parameter))
  577. {
  578. continue;
  579. }
  580. parameterNames.Add(parameter.Name.ToLower());
  581. OptionalAttribute optional = GetOptional(parameter);
  582. foreach (string altName in optional.AltNames)
  583. {
  584. parameterNames.Add(altName.ToLower());
  585. }
  586. }
  587. foreach (string optionalParameter in OptionalParameters(method))
  588. {
  589. string name = ParameterName(optionalParameter);
  590. if (!parameterNames.Contains(name.ToLower()))
  591. {
  592. throw new NConsolerException("Unknown parameter name {0}", optionalParameter);
  593. }
  594. }
  595. }
  596. private void ValidateMetadata()
  597. {
  598. CheckAnyActionMethodExists();
  599. IfActionMethodIsSingleCheckMethodHasParameters();
  600. foreach (MethodInfo method in _actionMethods)
  601. {
  602. CheckActionMethodNamesAreNotReserved();
  603. CheckRequiredAndOptionalAreNotAppliedAtTheSameTime(method);
  604. CheckOptionalParametersAreAfterRequiredOnes(method);
  605. CheckOptionalParametersDefaultValuesAreAssignableToRealParameterTypes(method);
  606. CheckOptionalParametersAltNamesAreNotDuplicated(method);
  607. }
  608. }
  609. private void CheckActionMethodNamesAreNotReserved()
  610. {
  611. foreach (MethodInfo method in _actionMethods)
  612. {
  613. if (method.Name.ToLower() == "help")
  614. {
  615. throw new NConsolerException("Method name \"{0}\" is reserved. Please, choose another name", method.Name);
  616. }
  617. }
  618. }
  619. private void CheckAnyActionMethodExists()
  620. {
  621. if (_actionMethods.Count == 0)
  622. {
  623. throw new NConsolerException("Can not find any public static method marked with [Action] attribute in type \"{0}\"", _targetType.Name);
  624. }
  625. }
  626. private void IfActionMethodIsSingleCheckMethodHasParameters()
  627. {
  628. if (_actionMethods.Count == 1 && _actionMethods[0].GetParameters().Length == 0)
  629. {
  630. throw new NConsolerException("[Action] attribute applied once to the method \"{0}\" without parameters. In this case NConsoler should not be used", _actionMethods[0].Name);
  631. }
  632. }
  633. private static void CheckRequiredAndOptionalAreNotAppliedAtTheSameTime(MethodBase method)
  634. {
  635. foreach (ParameterInfo parameter in method.GetParameters())
  636. {
  637. object[] attributes = parameter.GetCustomAttributes(typeof(ParameterAttribute), false);
  638. if (attributes.Length > 1)
  639. {
  640. throw new NConsolerException("More than one attribute is applied to the parameter \"{0}\" in the method \"{1}\"", parameter.Name, method.Name);
  641. }
  642. }
  643. }
  644. private static bool CanBeNull(Type type)
  645. {
  646. return type == typeof(string)
  647. || type == typeof(string[])
  648. || type == typeof(int[]);
  649. }
  650. private static void CheckOptionalParametersDefaultValuesAreAssignableToRealParameterTypes(MethodBase method)
  651. {
  652. foreach (ParameterInfo parameter in method.GetParameters())
  653. {
  654. if (IsRequired(parameter))
  655. {
  656. continue;
  657. }
  658. OptionalAttribute optional = GetOptional(parameter);
  659. // this causes a lot of unnecessary exceptions which is annoying during debug
  660. // if (optional.Default != null && optional.Default.GetType() == typeof(string) && CanBeConvertedToDate(optional.Default.ToString()))
  661. // {
  662. // return;
  663. // }
  664. if ((optional.Default == null && !CanBeNull(parameter.ParameterType))
  665. || (optional.Default != null && !optional.Default.GetType().IsAssignableFrom(parameter.ParameterType)))
  666. {
  667. throw new NConsolerException("Default value for an optional parameter \"{0}\" in method \"{1}\" can not be assigned to the parameter", parameter.Name, method.Name);
  668. }
  669. }
  670. }
  671. private static void CheckOptionalParametersAreAfterRequiredOnes(MethodBase method)
  672. {
  673. bool optionalFound = false;
  674. foreach (ParameterInfo parameter in method.GetParameters())
  675. {
  676. if (IsOptional(parameter))
  677. {
  678. optionalFound = true;
  679. }
  680. else if (optionalFound)
  681. {
  682. throw new NConsolerException("It is not allowed to write a parameter with a Required attribute after a parameter with an Optional one. See method \"{0}\" parameter \"{1}\"", method.Name, parameter.Name);
  683. }
  684. }
  685. }
  686. private static void CheckOptionalParametersAltNamesAreNotDuplicated(MethodBase method)
  687. {
  688. List<string> parameterNames = new List<string>();
  689. foreach (ParameterInfo parameter in method.GetParameters())
  690. {
  691. if (IsRequired(parameter))
  692. {
  693. parameterNames.Add(parameter.Name.ToLower());
  694. }
  695. else
  696. {
  697. if (parameterNames.Contains(parameter.Name.ToLower()))
  698. {
  699. throw new NConsolerException("Found duplicated parameter name \"{0}\" in method \"{1}\". Please check alt names for optional parameters", parameter.Name, method.Name);
  700. }
  701. parameterNames.Add(parameter.Name.ToLower());
  702. OptionalAttribute optional = GetOptional(parameter);
  703. foreach (string altName in optional.AltNames)
  704. {
  705. if (parameterNames.Contains(altName.ToLower()))
  706. {
  707. throw new NConsolerException("Found duplicated parameter name \"{0}\" in method \"{1}\". Please check alt names for optional parameters", altName, method.Name);
  708. }
  709. parameterNames.Add(altName.ToLower());
  710. }
  711. }
  712. }
  713. }
  714. #endregion
  715. }
  716. /// <summary>
  717. /// Used for getting messages from NConsoler
  718. /// </summary>
  719. public interface IMessenger
  720. {
  721. void Write(string message);
  722. }
  723. /// <summary>
  724. /// Uses Console class for message output
  725. /// </summary>
  726. public class ConsoleMessenger : IMessenger
  727. {
  728. public void Write(string message)
  729. {
  730. Console.WriteLine(message);
  731. }
  732. }
  733. /// <summary>
  734. /// Every action method should be marked with this attribute
  735. /// </summary>
  736. [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
  737. public sealed class ActionAttribute : Attribute
  738. {
  739. public ActionAttribute()
  740. {
  741. }
  742. public ActionAttribute(string description)
  743. {
  744. _description = description;
  745. }
  746. private string _description = String.Empty;
  747. /// <summary>
  748. /// Description is used for help messages
  749. /// </summary>
  750. public string Description
  751. {
  752. get
  753. {
  754. return _description;
  755. }
  756. set
  757. {
  758. _description = value;
  759. }
  760. }
  761. }
  762. /// <summary>
  763. /// Should not be used directly
  764. /// </summary>
  765. [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
  766. public class ParameterAttribute : Attribute
  767. {
  768. private string _description = String.Empty;
  769. /// <summary>
  770. /// Description is used in help message
  771. /// </summary>
  772. public string Description
  773. {
  774. get
  775. {
  776. return _description;
  777. }
  778. set
  779. {
  780. _description = value;
  781. }
  782. }
  783. protected ParameterAttribute()
  784. {
  785. }
  786. }
  787. /// <summary>
  788. /// Marks an Action method parameter as optional
  789. /// </summary>
  790. public sealed class OptionalAttribute : ParameterAttribute
  791. {
  792. private string[] _altNames;
  793. public string[] AltNames
  794. {
  795. get
  796. {
  797. return _altNames;
  798. }
  799. set
  800. {
  801. _altNames = value;
  802. }
  803. }
  804. private readonly object _defaultValue;
  805. public object Default
  806. {
  807. get
  808. {
  809. return _defaultValue;
  810. }
  811. }
  812. /// <param name="defaultValue">Default value if client doesn't pass this value</param>
  813. /// <param name="altNames">Aliases for parameter</param>
  814. public OptionalAttribute(object defaultValue, params string[] altNames)
  815. {
  816. _defaultValue = defaultValue;
  817. _altNames = altNames;
  818. }
  819. }
  820. /// <summary>
  821. /// Marks an Action method parameter as required
  822. /// </summary>
  823. public sealed class RequiredAttribute : ParameterAttribute
  824. {
  825. }
  826. /// <summary>
  827. /// Can be used for safe exception throwing - NConsoler will catch the exception
  828. /// </summary>
  829. public sealed class NConsolerException : Exception
  830. {
  831. public NConsolerException()
  832. {
  833. }
  834. public NConsolerException(string message, Exception innerException)
  835. : base(message, innerException)
  836. {
  837. }
  838. public NConsolerException(string message, params string[] arguments)
  839. : base(String.Format(message, arguments))
  840. {
  841. }
  842. }
  843. }