make.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. import Naked.toolshed.system as system
  4. import Naked.toolshed.python as python
  5. import Naked.toolshed.file as nfile
  6. import Naked.toolshed.ink as ink
  7. from Naked.toolshed.types import XDict, XString
  8. from Naked.toolshed.system import make_dirs, make_path, exit_success
  9. import datetime
  10. import sys
  11. ## TODO: Check for a local settings file (appname.yaml)
  12. ## TODO: make directories and files
  13. #------------------------------------------------------------------------------
  14. # [ MakeController class ]
  15. # Top level logic for the make command
  16. #------------------------------------------------------------------------------
  17. class MakeController:
  18. def __init__(self, app_name):
  19. self.app_name = app_name
  20. def run(self):
  21. if self.app_name == None:
  22. i = InfoCompiler(None)
  23. data_container = i.getSetupFileInfo()
  24. else:
  25. i = InfoCompiler(self.app_name)
  26. data_container = i.getUserInfo()
  27. db = DirectoryBuilder(data_container)
  28. db.build()
  29. fb = FileBuilder(data_container)
  30. if fb.build_and_write(): # file writes were successful
  31. main_script_path = make_path(data_container.app_name, 'lib', data_container.app_name, 'app.py')
  32. settings_path = make_path(data_container.app_name, 'lib', data_container.app_name, 'settings.py')
  33. command_dir = make_path(data_container.app_name, 'lib', data_container.app_name, 'commands')
  34. setuppy_path = make_path(data_container.app_name, 'setup.py')
  35. print(" ")
  36. print(data_container.app_name + " was successfully built.")
  37. print(" ")
  38. print("-----")
  39. print("Main application script: " + main_script_path)
  40. print("Settings file: " + settings_path)
  41. print("Commands directory: " + command_dir)
  42. print("setup.py file: " + setuppy_path)
  43. print("-----")
  44. print(" ")
  45. print("Use 'python setup.py develop' from the top level of your project and you can begin testing your application with the executable, " + data_container.app_name)
  46. exit_success()
  47. #------------------------------------------------------------------------------
  48. # [ InfoCompiler class ]
  49. # obtain information from user in order to build a new project
  50. #------------------------------------------------------------------------------
  51. class InfoCompiler:
  52. def __init__(self, app_name):
  53. self.data = DataContainer()
  54. self.data.app_name = app_name
  55. self.displayed_info_flag = 0
  56. def getUserInfo(self):
  57. if not self.displayed_info_flag:
  58. print("We need some information to create your project.")
  59. self.displayed_info_flag = 1
  60. # If no project name, then query for it because this is mandatory
  61. if self.data.app_name == None:
  62. if python.is_py2:
  63. response = raw_input("Please enter your application name (q=quit): ")
  64. else:
  65. response = input("Please enter your application name (q=quit): ")
  66. if len(response) > 0:
  67. if response == "q":
  68. print("Aborted project build.")
  69. sys.exit(0) # user requested quit
  70. else:
  71. if len(response.split()) > 1: # if more than one word
  72. print("The application name must be a single word. Please try again.")
  73. self.getUserInfo()
  74. else:
  75. self.data.app_name = response
  76. else:
  77. print("The Naked project will not build without an application name. Please try again.")
  78. return self.getUserInfo()
  79. # if project name already set, then obtain the other optional information
  80. if python.is_py2():
  81. self.data.developer = raw_input("Enter the licensing developer or organization (q=quit): ")
  82. if self.data.developer == "q":
  83. print("Aborted the project build.")
  84. sys.exit(0)
  85. self.data.license = raw_input("Enter the license type (or leave blank, q=quit): ")
  86. if self.data.license == "q":
  87. print("Aborted the project build.")
  88. sys.exit(0)
  89. else:
  90. self.data.developer = input("Enter the licensing developer or organization: ")
  91. if self.data.developer == "q":
  92. print("Aborted the project build.")
  93. sys.exit(0)
  94. self.data.license = input("Enter the license type (or leave blank): ")
  95. if self.data.license == "q":
  96. print("Aborted the project build.")
  97. sys.exit(0)
  98. if self.confirmData():
  99. return self.data
  100. else:
  101. print("Let's try again...")
  102. return self.getUserInfo() # try again
  103. def getSetupFileInfo(self):
  104. files = system.list_all_files_cwd()
  105. if len(files) > 0:
  106. setupfile_exists = False
  107. for a_file in files:
  108. if 'naked.yaml' == a_file.lower(): # accepts any permutation of upper/lower case 'naked.yaml'
  109. print("Detected a Naked project YAML setup file (" + a_file + ").")
  110. setupfile_exists = True
  111. fr = nfile.FileReader(a_file)
  112. the_yaml = fr.read_utf8()
  113. self.parseYaml(the_yaml)
  114. if setupfile_exists:
  115. if self.confirmData():
  116. return self.data
  117. else:
  118. print("Aborted the project build.")
  119. if python.is_py2():
  120. response = raw_input("Would you like to modify this information? (y/n) ")
  121. else:
  122. response = input("Would you like to modify this information? (y/n) ")
  123. if response in ['y', 'Y', 'Yes', 'YES', 'yes']:
  124. self.displayed_info_flag = 1
  125. self.data.app_name = None
  126. return self.getUserInfo() # return the result from the getUserInfo command to the calling method
  127. else:
  128. sys.exit(0)
  129. else:
  130. return self.getUserInfo() # there are files but no setup file, use the manual entry method
  131. else:
  132. return self.getUserInfo() # there are no files in the directory, use the manual entry method
  133. def parseYaml(self, yaml_string):
  134. import yaml
  135. the_yaml = yaml.load(yaml_string)
  136. # Parse project name
  137. if 'application' in the_yaml:
  138. self.data.app_name = the_yaml['application']
  139. else:
  140. print("Unable to find the application name ('application' field) in naked.yaml")
  141. if python.is_py2:
  142. response = raw_input("Please enter your application name: ")
  143. else:
  144. response = input("Please enter your application name: ")
  145. if len(response) > 0:
  146. self.data.app_name = response # assign the application name at CL if was not entered in file
  147. else:
  148. print("The Naked project will not build without an application name. Please try again.")
  149. self.displayed_info_flag = 1
  150. self.getUserInfo()
  151. # Parse developer
  152. if 'developer' in the_yaml:
  153. self.data.developer = the_yaml['developer'] # set developer
  154. else:
  155. self.data.developer = ""
  156. # Parse license type
  157. if 'license' in the_yaml:
  158. self.data.license = the_yaml['license'] # set license
  159. else:
  160. self.data.license = ""
  161. def confirmData(self):
  162. templ_str = getHeaderTemplate()
  163. template = ink.Template(templ_str)
  164. renderer = ink.Renderer(template, {'app_name': self.data.app_name, 'developer': self.data.developer, 'license': self.data.license, 'year': self.data.year})
  165. display_header = renderer.render()
  166. print("\nPlease confirm the information below:")
  167. print(display_header)
  168. if python.is_py2():
  169. response = raw_input("Is this correct? (y/n) ")
  170. else:
  171. response = input("Is this correct? (y/n) ")
  172. if response in ['y', 'Y', 'yes', 'YES']:
  173. return True
  174. else:
  175. self.data.app_name = None
  176. return False
  177. #------------------------------------------------------------------------------
  178. # [ getHeaderTemplate function ] (string)
  179. # returns the Ink header template for user confirmation
  180. #------------------------------------------------------------------------------
  181. def getHeaderTemplate():
  182. templ_str = """
  183. ----------------------------------------------------------
  184. {{app_name}}
  185. Copyright {{year}} {{developer}}
  186. {{license}}
  187. ----------------------------------------------------------
  188. """
  189. return templ_str
  190. #------------------------------------------------------------------------------
  191. # [ DataContainer class ]
  192. # state maintenance object that holds project information
  193. #------------------------------------------------------------------------------
  194. class DataContainer:
  195. def __init__(self):
  196. self.cwd = system.cwd()
  197. self.year = str(datetime.datetime.now().year)
  198. #------------------------------------------------------------------------------
  199. # [ DirectoryBuilder class ]
  200. # generation of directory structure for a new project
  201. #------------------------------------------------------------------------------
  202. class DirectoryBuilder:
  203. def __init__(self, data_container):
  204. self.data_container = data_container
  205. def build(self):
  206. top_level_dir = self.data_container.app_name
  207. second_level_dirs = ['docs', 'lib', 'tests']
  208. lib_subdir = make_path(self.data_container.app_name, 'commands')
  209. for xdir in second_level_dirs:
  210. make_dirs(make_path(top_level_dir, xdir))
  211. make_dirs(make_path(top_level_dir, 'lib', lib_subdir))
  212. #------------------------------------------------------------------------------
  213. # [ FileBuilder class ]
  214. # generate the files for a new project
  215. #------------------------------------------------------------------------------
  216. class FileBuilder:
  217. def __init__(self, data_container):
  218. self.data_container = data_container
  219. self.file_dictionary = {}
  220. def build_and_write(self):
  221. self._make_file_paths() # create the file paths for all generated files
  222. self._render_file_strings() # create the rendered template strings
  223. self._make_file_dictionary() # make the file path : file string dictionary
  224. self.write_files() # write out to files
  225. return True # if made it this far without exception, return True to calling method to confirm file writes
  226. # files are included in self.file_dictionary as key = filepath, value = filestring pairs
  227. # write the files to disk
  228. def write_files(self):
  229. the_file_xdict = XDict(self.file_dictionary)
  230. for filepath, file_string in the_file_xdict.xitems():
  231. fw = nfile.FileWriter(filepath)
  232. try:
  233. fw.write_utf8(file_string)
  234. except TypeError as te: # catch unicode write errors
  235. fw.write(file_string)
  236. def _make_file_paths(self):
  237. from Naked.toolshed.system import make_path
  238. self.top_manifestin = make_path(self.data_container.app_name, 'MANIFEST.in')
  239. self.top_readmemd = make_path(self.data_container.app_name, 'README.md')
  240. self.top_setupcfg = make_path(self.data_container.app_name, 'setup.cfg')
  241. self.top_setuppy = make_path(self.data_container.app_name, 'setup.py')
  242. self.docs_license = make_path(self.data_container.app_name, 'docs', 'LICENSE')
  243. self.docs_readmerst = make_path(self.data_container.app_name, 'docs', 'README.rst')
  244. self.lib_initpy = make_path(self.data_container.app_name, 'lib', '__init__.py')
  245. self.com_initpy = make_path(self.data_container.app_name, 'lib', self.data_container.app_name, 'commands', '__init__.py')
  246. self.tests_initpy = make_path(self.data_container.app_name, 'tests', '__init__.py')
  247. self.lib_profilerpy = make_path(self.data_container.app_name, 'lib', 'profiler.py')
  248. self.lib_proj_initpy = make_path(self.data_container.app_name, 'lib', self.data_container.app_name, '__init__.py')
  249. self.lib_proj_apppy = make_path(self.data_container.app_name, 'lib', self.data_container.app_name, 'app.py')
  250. self.lib_proj_settingspy = make_path(self.data_container.app_name, 'lib', self.data_container.app_name, 'settings.py')
  251. def _render_file_strings(self):
  252. from Naked.templates.manifest_in_file import manifest_file_string
  253. from Naked.templates.readme_md_file import readme_md_string
  254. from Naked.templates.setup_cfg_file import setup_cfg_string
  255. from Naked.templates.setup_py_file import setup_py_string
  256. from Naked.templates.profiler_file import profiler_file_string
  257. from Naked.templates.app_file import app_file_string
  258. from Naked.templates.settings_file import settings_file_string
  259. data_dict = self.data_container.__dict__
  260. self.top_manifestin_rendered = manifest_file_string # no replacements necessary
  261. self.top_readmemd_rendered = self._render_template(self._create_template(readme_md_string), data_dict) #requires app_name replacement
  262. self.top_setupcfg_rendered = setup_cfg_string # no replacement necessary
  263. self.top_setuppy_rendered = self._render_template(self._create_template(setup_py_string), data_dict) # requires app_name, developer replacements
  264. self.docs_readmerst_rendered = "" # blank document stub write
  265. self.lib_profilerpy_rendered = profiler_file_string # no replacement necessary
  266. self.initpy_rendered = "" # blank __init__.py files
  267. self.lib_proj_apppy_rendered = self._render_template(self._create_template(app_file_string), data_dict) # requires app_name, developer, license_name, year replacements
  268. self.lib_proj_settingspy_rendered = self._render_template(self._create_template(settings_file_string), data_dict) # requires app_name replacement
  269. if len(self.data_container.license) > 0:
  270. license = self.parse_licenses(self.data_container.license) # find the appropriate license template if the license was provided by user
  271. if len(license) > 0: # could be empty string if fails to match a license template provided by Naked
  272. self.docs_license_rendered = self._render_template(self._create_template(license), data_dict)
  273. else:
  274. self.docs_license_rendered = ""
  275. def _make_file_dictionary(self):
  276. file_dictionary = {}
  277. ## File path : file string key/value pairs > make as XString and encode as unicode for unicode file writes
  278. file_dictionary[self.top_manifestin] = XString(self.top_manifestin_rendered).unicode().strip()
  279. file_dictionary[self.top_readmemd] = XString(self.top_readmemd_rendered).unicode().strip()
  280. file_dictionary[self.top_setupcfg] = XString(self.top_setupcfg_rendered).unicode().strip()
  281. file_dictionary[self.top_setuppy] = XString(self.top_setuppy_rendered).unicode().strip()
  282. file_dictionary[self.docs_license] = XString(self.docs_license_rendered).unicode().strip()
  283. file_dictionary[self.docs_readmerst] = XString(self.docs_readmerst_rendered).unicode().strip()
  284. file_dictionary[self.lib_initpy] = XString(self.initpy_rendered).unicode().strip()
  285. file_dictionary[self.com_initpy] = XString(self.initpy_rendered).unicode().strip()
  286. file_dictionary[self.tests_initpy] = XString(self.initpy_rendered).unicode().strip()
  287. file_dictionary[self.lib_profilerpy] = XString(self.lib_profilerpy_rendered).unicode().strip()
  288. file_dictionary[self.lib_proj_initpy] = XString(self.initpy_rendered).unicode().strip()
  289. file_dictionary[self.lib_proj_apppy] = XString(self.lib_proj_apppy_rendered).unicode().strip()
  290. file_dictionary[self.lib_proj_settingspy] = XString(self.lib_proj_settingspy_rendered).unicode().strip()
  291. self.file_dictionary = file_dictionary
  292. def _create_template(self, template_string):
  293. return ink.Template(template_string)
  294. def _render_template(self, template, key_dict):
  295. r = ink.Renderer(template, key_dict)
  296. return r.render()
  297. def parse_licenses(self, license_string):
  298. if len(license_string) > 0:
  299. license = license_string.lower() # case insensitive matching, make lower case version
  300. if license.startswith('apache'):
  301. from Naked.templates.licenses import apache_license
  302. return apache_license
  303. elif license.startswith('bsd'):
  304. from Naked.templates.licenses import bsd_license
  305. return bsd_license
  306. elif license.startswith('gpl'):
  307. from Naked.templates.licenses import gpl3_license
  308. return gpl3_license
  309. elif license.startswith('lgpl'):
  310. from Naked.templates.licenses import lgpl_license
  311. return lgpl_license
  312. elif license.startswith('mit'):
  313. from Naked.templates.licenses import mit_license
  314. return mit_license
  315. elif license.startswith('mozilla'):
  316. from Naked.templates.licenses import mozilla_license
  317. return mozilla_license
  318. else:
  319. return ""
  320. def help():
  321. from Naked.toolshed.system import exit_success
  322. help_string = """
  323. Naked make Command Help
  324. =======================
  325. The make command builds a new Naked project. The project can be built from either responses that you give on the command line, or from a naked.yaml project settings file.
  326. USAGE
  327. naked make [argument]
  328. The command should be run in the top level of the path where you would like to create your project. The argument to the make command is optional. If used, this is the name of your new project. It is not necessary to include the argument if you use a naked.yaml project settings file.
  329. The naked.yaml settings file has the following structure:
  330. application: <your project name>
  331. developer: <developer name>
  332. license: <license type>
  333. Place this in the top level of an empty directory and use `naked make` in the same directory. Naked will confirm your settings and then build the project directories and files from these settings.
  334. SECONDARY COMMANDS
  335. none
  336. OPTIONS
  337. none
  338. EXAMPLES
  339. naked make
  340. naked make testapp"""
  341. print(help_string)
  342. exit_success()
  343. if __name__ == '__main__':
  344. pass