mkx 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. #!/bin/bash
  2. usage() {
  3. local self
  4. self="$(basename "$0")"
  5. warn "usage: $self [-f|--force] [+TEMPLATE] FILE"
  6. warn "usage: $self -l|--list"
  7. exit 2
  8. }
  9. warn() {
  10. #
  11. # Print warning
  12. #
  13. echo "$@" >&2
  14. }
  15. die() {
  16. #
  17. # Warn and exit prematurely
  18. #
  19. warn "$@"
  20. exit 3
  21. }
  22. lstemplates() {
  23. #
  24. # Print list of valid templates
  25. #
  26. echo sed
  27. echo pya
  28. echo shellfu
  29. echo Bash
  30. echo Lua
  31. echo Perl
  32. echo py3
  33. echo Python3
  34. echo Python
  35. }
  36. #shellcheck disable=SC2016
  37. mktemplate() {
  38. #
  39. # Print template $Template for $FilePath
  40. #
  41. case "$Template" in
  42. sh)
  43. echo "#!/bin/sh"
  44. echo ''
  45. echo ''
  46. ;;
  47. bash|Bash)
  48. echo "#!/bin/bash"
  49. echo ''
  50. echo ''
  51. ;;
  52. sed)
  53. echo "#!/bin/sed -f"
  54. echo ''
  55. echo ''
  56. ;;
  57. lua|Lua)
  58. echo "#!$(which lua)"
  59. echo ''
  60. ;;
  61. pl|Perl)
  62. echo "#!$(which perl)"
  63. echo ''
  64. echo 'use strict;'
  65. echo 'use warnings;'
  66. echo ''
  67. echo ''
  68. ;;
  69. py|Python)
  70. echo "#!$(which python)"
  71. echo ''
  72. echo "if __name__ == '__main__':"
  73. echo ' '
  74. ;;
  75. py3|Python3)
  76. echo "#!/usr/bin/python3"
  77. echo ''
  78. echo 'import sys'
  79. echo ''
  80. echo ''
  81. echo 'class UsageError(ValueError):'
  82. echo ' pass'
  83. echo ''
  84. echo ''
  85. echo 'def usage():'
  86. echo ' self = sys.argv[0]'
  87. echo ' raise UsageError("usage: %s [-j|-t|-p] YAML_FILE.." % self)'
  88. echo ''
  89. echo ''
  90. echo 'def main(argv):'
  91. echo ' if len(argv) < 2:'
  92. echo ' usage()'
  93. echo ''
  94. echo ''
  95. echo 'if __name__ == "__main__":'
  96. echo ' main(sys.argv[1:])'
  97. ;;
  98. pya)
  99. echo '#!/usr/bin/python3'
  100. echo '"""'
  101. echo 'A simple application'
  102. echo '"""'
  103. echo ''
  104. echo 'import sys'
  105. echo 'import traceback'
  106. echo ''
  107. echo ''
  108. echo 'class UsageError(ValueError):'
  109. echo ' pass'
  110. echo ''
  111. echo ''
  112. echo 'class AppError(RuntimeError):'
  113. echo ' pass'
  114. echo ''
  115. echo ''
  116. echo 'class BaseApp:'
  117. echo ' """'
  118. echo ' Main application class'
  119. echo ' """'
  120. echo ''
  121. echo ' @classmethod'
  122. echo ' def main(cls, factory=None):'
  123. echo ' factory = factory if factory else cls.from_argv'
  124. echo ' try:'
  125. echo ' app = factory()'
  126. echo ' es = app.run()'
  127. echo ' except UsageError as e:'
  128. echo ' print(e, file=sys.stderr)'
  129. echo ' sys.exit(2)'
  130. echo ' except AppError as e:'
  131. echo ' print(e, file=sys.stderr)'
  132. echo ' sys.exit(3)'
  133. echo ' except Exception:'
  134. echo ' sys.stderr.write(traceback.format_exc())'
  135. echo ' sys.exit(4)'
  136. echo ' if type(es) is not int:'
  137. echo ' msg = ('
  138. echo ' "%s.run() did not return proper exit status: %r is not int"'
  139. echo ' % (app.__class__.__name__, es)'
  140. echo ' )'
  141. echo ' BaseApp.WARN(msg)'
  142. echo ' es = 4'
  143. echo ' sys.exit(es)'
  144. echo ''
  145. echo ' @classmethod'
  146. echo ' def from_argv(cls):'
  147. echo ' return cls('
  148. echo ' name=sys.argv[0],'
  149. echo ' args=sys.argv[1:]'
  150. echo ' )'
  151. echo ''
  152. echo ' def __init__(self, name, args):'
  153. echo ' self.name = name'
  154. echo ' self.args = args'
  155. echo ''
  156. echo ' def _throw_usage(self, pattern=None):'
  157. echo ' """'
  158. echo ' Throw UsageError with pattern *pattern*.'
  159. echo ' """'
  160. echo ' parts = ["usage:", self.name]'
  161. echo ' if pattern:'
  162. echo ' parts.append(pattern)'
  163. echo ' raise UsageError(" ".join(parts))'
  164. echo ''
  165. echo ' @staticmethod'
  166. echo ' def WARN(msg):'
  167. echo ' """'
  168. echo ' Warn user with message *msg*. *msg* can be string or a list of lines.'
  169. echo ' """'
  170. echo ' if type(msg) is list:'
  171. echo ' lines = msg'
  172. echo ' else:'
  173. echo ' lines = [str(msg)]'
  174. echo ' for line in lines:'
  175. echo ' print(line, file=sys.stderr)'
  176. echo ''
  177. echo ' def _validate(self):'
  178. echo ' """'
  179. echo ' Validate *self.args*; throw UsageError if needed.'
  180. echo ' """'
  181. echo ' if not self.args:'
  182. echo ' self._throw_usage("ARG..")'
  183. echo ''
  184. echo ' def run(self):'
  185. echo ' """'
  186. echo ' Run the application; return integer exit status'
  187. echo ' """'
  188. echo ' self._validate()'
  189. echo ' if self.args[0] == "--fail":'
  190. echo ' raise AppError("you asked for it")'
  191. echo ' if self.args[0] == "--fail-unexpectedly":'
  192. echo ' raise RuntimeError("you asked for it ANyway")'
  193. echo ' if self.args[0] == "--warn":'
  194. echo ' BaseApp.WARN("you asked for a warning")'
  195. echo ' return 0'
  196. echo ' if self.args[0] == "--badret":'
  197. echo ' return object()'
  198. echo ' return 0'
  199. echo ''
  200. echo ''
  201. echo 'class App(BaseApp):'
  202. echo ''
  203. echo ' def _validate(self):'
  204. echo ' """'
  205. echo ' Validate *self.args*; throw UsageError as needed.'
  206. echo ''
  207. echo ' Prefer using self._throw_usage().'
  208. echo ' """'
  209. echo ' App.WARN("You need to rewrite App._validate()!!!")'
  210. echo ' super()._validate()'
  211. echo ''
  212. echo ' def run(self):'
  213. echo ' """'
  214. echo ' Run the application; return integer exit status'
  215. echo ' """'
  216. echo ' App.WARN("You need to rewrite App.run()")'
  217. echo ' super().run()'
  218. echo ''
  219. echo ''
  220. echo 'if __name__ == "__main__":'
  221. echo ' App.main()'
  222. ;;
  223. shellfu)
  224. echo "#!/bin/bash"
  225. echo ""
  226. echo '#shellcheck disable=SC1090'
  227. echo '. "$(sfpath)" || exit 3'
  228. echo ''
  229. echo 'shellfu import pretty'
  230. echo ''
  231. echo 'usage() {'
  232. echo ' mkusage "$@" "[-d] [-v] ARG..."'
  233. echo '}'
  234. echo ''
  235. echo 'main() {'
  236. echo ' while true; do case $1 in'
  237. echo ' -d) PRETTY_DEBUG=true; shift ;;'
  238. echo ' -v) PRETTY_VERBOSE=true; shift ;;'
  239. echo ' -*) usage -w "unknown argument: $1" ;;'
  240. echo ' *) break ;;'
  241. echo ' esac done'
  242. echo '}'
  243. echo ''
  244. echo 'main "$@"'
  245. ;;
  246. *)
  247. die "unknown template or language: $Template"
  248. ;;
  249. esac
  250. }
  251. guess_language() {
  252. #
  253. # Guess intended language from filepath $FilePath
  254. #
  255. local fname # file name
  256. fname="$(basename "$FilePath")"
  257. case "$fname" in
  258. *.*) echo "${fname##*.}" ;;
  259. *) echo "$MKX_DEFAULT_LANG" ;;
  260. esac
  261. }
  262. #
  263. # Default language
  264. #
  265. MKX_DEFAULT_LANG=${MKX_DEFAULT_LANG:-sh}
  266. main() {
  267. local FilePath # destination file path
  268. local Template # built-in template name
  269. local careful # ignore existing file
  270. careful=true
  271. while true; do case $1 in
  272. -f|--force) careful=false; shift ;;
  273. -l|--list) lstemplates; exit 0 ;;
  274. -*) usage ;;
  275. +*) Template="${1:1}"; shift ;;
  276. *) break ;;
  277. esac done
  278. FilePath="$1"
  279. test -n "$FilePath" || usage
  280. test -f "$FilePath" && $careful && die "file already exists: $FilePath"
  281. test -n "$Template" || Template=$(guess_language)
  282. mktemplate > "$FilePath"
  283. chmod +x "$FilePath"
  284. }
  285. main "$@"