comparison piecrust/commands/builtin/themes.py @ 747:5336e146ac8d

themes: Simplify `themes` command.
author Ludovic Chabant <ludovic@chabant.com>
date Thu, 09 Jun 2016 22:25:16 -0700
parents a00750896316
children 549e21789ad9
comparison
equal deleted inserted replaced
746:0fdf1e43bf92 747:5336e146ac8d
4 import logging 4 import logging
5 import yaml 5 import yaml
6 from piecrust import ( 6 from piecrust import (
7 RESOURCES_DIR, THEME_DIR, THEME_CONFIG_PATH, THEME_INFO_PATH) 7 RESOURCES_DIR, THEME_DIR, THEME_CONFIG_PATH, THEME_INFO_PATH)
8 from piecrust.commands.base import ChefCommand 8 from piecrust.commands.base import ChefCommand
9 from piecrust.pathutil import SiteNotFoundError
9 10
10 11
11 logger = logging.getLogger(__name__) 12 logger = logging.getLogger(__name__)
12 13
13 14
16 super(ThemesCommand, self).__init__() 17 super(ThemesCommand, self).__init__()
17 self.name = 'themes' 18 self.name = 'themes'
18 self.description = "Manage the themes for the current website." 19 self.description = "Manage the themes for the current website."
19 20
20 def setupParser(self, parser, app): 21 def setupParser(self, parser, app):
21 if app.root_dir is None:
22 return
23
24 subparsers = parser.add_subparsers() 22 subparsers = parser.add_subparsers()
25 p = subparsers.add_parser( 23 p = subparsers.add_parser(
26 'info', 24 'info',
27 help="Provides information about the current theme.") 25 help="Provides information about the current theme.")
28 p.set_defaults(sub_func=self._info) 26 p.set_defaults(sub_func=self._info)
29 27
30 p = subparsers.add_parser( 28 p = subparsers.add_parser(
31 'create',
32 help="Create a new theme for the current website.")
33 p.add_argument(
34 '--from-default',
35 action='store_true',
36 help=("Create a new theme by copying the default PieCrust "
37 "theme into the theme directory"))
38 p.add_argument(
39 'theme_name',
40 help=("The name of the theme"))
41 p.set_defaults(sub_func=self._createTheme)
42
43 p = subparsers.add_parser(
44 'override', 29 'override',
45 help="Copies a theme to the website for customization.") 30 help="Copies the current theme to the website for "
31 "customization.")
46 p.set_defaults(sub_func=self._overrideTheme) 32 p.set_defaults(sub_func=self._overrideTheme)
47 33
48 p = subparsers.add_parser( 34 p = subparsers.add_parser(
49 'activate', 35 'link',
50 help="Makes a given theme the active one for the current " 36 help="Makes a given theme the active one for the current "
51 "website by creating a symbolic link to it from the " 37 "website by creating a symbolic link to it from the "
52 "'theme' directory. If a symbolic link can't be created " 38 "'theme' directory.")
53 "the theme will be copied to the 'theme' directory.")
54 p.add_argument( 39 p.add_argument(
55 'theme_dir', 40 'theme_dir',
56 help="The directory of the theme to link.") 41 help="The directory of the theme to link.")
57 p.add_argument( 42 p.set_defaults(sub_func=self._linkTheme)
58 '--link-only',
59 action='store_true',
60 help="Abort the activation if a symbolic link can't be "
61 "created")
62 p.set_defaults(sub_func=self._activateTheme)
63 43
64 p = subparsers.add_parser( 44 p = subparsers.add_parser(
65 'deactivate', 45 'unlink',
66 help="Removes the currently active theme for the website. " 46 help="Removes the currently active theme for the website. "
67 "This removes the symbolic link to the theme, if any, or " 47 "This removes the symbolic link to the theme, if any, or "
68 "deletes the theme folder if it was copied locally.") 48 "deletes the theme folder if it was copied locally.")
69 p.set_defaults(sub_func=self._deactivateTheme) 49 p.set_defaults(sub_func=self._unlinkTheme)
70 50
71 def checkedRun(self, ctx): 51 def checkedRun(self, ctx):
52 if ctx.app.root_dir is None:
53 raise SiteNotFoundError(theme=ctx.app.theme_site)
54
72 if not hasattr(ctx.args, 'sub_func'): 55 if not hasattr(ctx.args, 'sub_func'):
73 ctx.args = ctx.parser.parse_args(['themes', 'info']) 56 ctx.args = ctx.parser.parse_args(['themes', 'info'])
74 ctx.args.sub_func(ctx) 57 ctx.args.sub_func(ctx)
75 58
76 def _info(self, ctx): 59 def _info(self, ctx):
77 theme_dir = os.path.join(ctx.app.root_dir, THEME_DIR) 60 theme_dir = ctx.app.theme_dir
78 if not os.path.exists(theme_dir): 61 if not os.path.exists(theme_dir):
79 logger.info("Using default theme, from: %s" % ctx.app.theme_dir) 62 logger.info("Using default theme, from: %s" % ctx.app.theme_dir)
80 elif os.path.islink(theme_dir): 63 elif theme_dir.startswith(ctx.app.root_dir):
81 target = os.readlink(theme_dir) 64 if os.path.islink(theme_dir):
82 target = os.path.join(os.path.dirname(theme_dir), target) 65 target = os.readlink(theme_dir)
83 logger.info("Using theme, from: %s" % target) 66 target = os.path.join(os.path.dirname(theme_dir), target)
67 logger.info("Using local theme, from: %s" % target)
68 else:
69 logger.info("Using local theme.")
84 else: 70 else:
85 logger.info("Using local theme.") 71 logger.info("Using theme from: %s" % theme_dir)
86 72
87 def _createTheme(self, ctx): 73 info_path = os.path.join(theme_dir, THEME_CONFIG_PATH)
88 theme_dir = os.path.join(ctx.app.root_dir, THEME_DIR) 74 if os.path.exists(info_path):
89 if os.path.exists(theme_dir): 75 info = None
90 logger.warning("A theme already exists, and will be overwritten. " 76 with open(info_path, 'r', encoding='utf8') as fp:
91 "Are you sure? [Y/n]") 77 theme_cfg = yaml.load(fp.read())
92 ans = input() 78 if isinstance(theme_cfg, dict):
93 if len(ans) > 0 and ans.lower() not in ['y', 'yes']: 79 info = theme_cfg.get('theme')
94 return 1 80 if info:
95 81 logger.info("Theme info:")
96 shutil.rmtree(theme_dir) 82 for k, v in info.items():
97 83 logger.info(" - %s: %s" % (str(k), str(v)))
98 try:
99 if ctx.args.from_default:
100 def reporting_copy2(src, dst):
101 rel_dst = os.path.relpath(dst, ctx.app.root_dir)
102 logger.info(rel_dst)
103 shutil.copy2(src, dst)
104
105 default_theme_dir = os.path.join(RESOURCES_DIR, 'theme')
106 shutil.copytree(default_theme_dir, theme_dir,
107 copy_function=reporting_copy2)
108 return 0
109
110 logger.info("Creating theme directory.")
111 os.makedirs(theme_dir)
112
113 logger.info("Creating theme_config.yml")
114 config_path = os.path.join(theme_dir, THEME_CONFIG_PATH)
115 with open(config_path, 'w', encoding='utf8') as fp:
116 fp.write('')
117
118 logger.info("Creating theme_info.yml")
119 info_path = os.path.join(theme_dir, THEME_INFO_PATH)
120 with open(info_path, 'w', encoding='utf8') as fp:
121 yaml.dump(
122 {
123 'name': ctx.args.theme_name or 'My New Theme',
124 'description': "A new PieCrust theme.",
125 'authors': ['Your Name Here <email or twitter>'],
126 'url': 'http://www.example.org'},
127 fp,
128 default_flow_style=False)
129 return 0
130 except:
131 logger.error("Error occured, deleting theme directory.")
132 shutil.rmtree(theme_dir)
133 raise
134 84
135 def _overrideTheme(self, ctx): 85 def _overrideTheme(self, ctx):
136 app_dir = ctx.app.root_dir
137 theme_dir = ctx.app.theme_dir 86 theme_dir = ctx.app.theme_dir
138 if not theme_dir: 87 if not theme_dir:
139 logger.error("There is not theme currently applied to override.") 88 logger.error("There is no theme currently applied.")
140 return 1 89 return 1
141 90
142 copies = [] 91 copies = []
92 app_dir = ctx.app.root_dir
143 for dirpath, dirnames, filenames in os.walk(theme_dir): 93 for dirpath, dirnames, filenames in os.walk(theme_dir):
144 rel_dirpath = os.path.relpath(dirpath, theme_dir) 94 rel_dirpath = os.path.relpath(dirpath, theme_dir)
145 for name in filenames: 95 for name in filenames:
146 if (dirpath == theme_dir and 96 if (dirpath == theme_dir and
147 name in [THEME_CONFIG_PATH, THEME_INFO_PATH]): 97 name in [THEME_CONFIG_PATH, THEME_INFO_PATH]):
167 logger.info(os.path.relpath(c[1], app_dir)) 117 logger.info(os.path.relpath(c[1], app_dir))
168 if not os.path.exists(os.path.dirname(c[1])): 118 if not os.path.exists(os.path.dirname(c[1])):
169 os.makedirs(os.path.dirname(c[1])) 119 os.makedirs(os.path.dirname(c[1]))
170 shutil.copy2(c[0], c[1]) 120 shutil.copy2(c[0], c[1])
171 121
172 def _activateTheme(self, ctx): 122 def _linkTheme(self, ctx):
173 if not os.path.isdir(ctx.args.theme_dir): 123 if not os.path.isdir(ctx.args.theme_dir):
174 logger.error("Invalid theme directory: %s" % ctx.args.theme_dir) 124 logger.error("Invalid theme directory: %s" % ctx.args.theme_dir)
175 return 1 125 return 1
176 126
127 msg = ("A theme already exists, and will be deleted. "
128 "Are you sure? [Y/n]")
129 self._doUnlinkTheme(ctx.app.root_dir, msg)
130
177 theme_dir = os.path.join(ctx.app.root_dir, THEME_DIR) 131 theme_dir = os.path.join(ctx.app.root_dir, THEME_DIR)
178
179 if os.path.islink(theme_dir):
180 logger.debug("Unlinking: %s" % theme_dir)
181 os.unlink(theme_dir)
182 elif os.path.isdir(theme_dir):
183 logger.warning("A theme already exists, and will be overwritten. "
184 "Are you sure? [Y/n]")
185 ans = input()
186 if len(ans) > 0 and ans.lower() not in ['y', 'yes']:
187 return 1
188
189 shutil.rmtree(theme_dir)
190
191 try: 132 try:
192 os.symlink(ctx.args.theme_dir, theme_dir) 133 os.symlink(ctx.args.theme_dir, theme_dir)
193 except (NotImplementedError, OSError) as ex: 134 except (NotImplementedError, OSError) as ex:
194 if ctx.args.link_only: 135 if ctx.args.link_only:
195 logger.error("Couldn't symlink the theme: %s" % ex) 136 logger.error("Couldn't symlink the theme: %s" % ex)
196 return 1 137 return 1
197 138
198 # pre-Vista Windows, or unprivileged user... gotta copy the 139 def _unlinkTheme(self, ctx):
199 # theme the old fashioned way. 140 msg = ("The active theme is local. Are you sure you want "
200 logging.warning("Can't create a symbolic link... copying the " 141 "to delete the theme directory? [Y/n]")
201 "theme directory instead.") 142 self._doUnlinkTheme(ctx.app.root_dir, msg)
202 ignore = shutil.ignore_patterns('.git*', '.hg*', '.svn', '.bzr')
203 shutil.copytree(ctx.args.theme_dir, theme_dir, ignore=ignore)
204 143
205 def _deactivateTheme(self, ctx): 144 def _doUnlinkTheme(self, root_dir, delete_message):
206 theme_dir = os.path.join(ctx.app.root_dir, THEME_DIR) 145 theme_dir = os.path.join(root_dir, THEME_DIR)
207 146
208 if os.path.islink(theme_dir): 147 if os.path.islink(theme_dir):
209 logger.debug("Unlinking: %s" % theme_dir) 148 logger.debug("Unlinking: %s" % theme_dir)
210 os.unlink(theme_dir) 149 os.unlink(theme_dir)
211 elif os.path.isdir(theme_dir): 150 return True
212 logger.warning("The active theme is local. Are you sure you want " 151
213 "to delete the theme directory? [Y/n]") 152 if os.path.isdir(theme_dir):
153 logger.warning(delete_message)
214 ans = input() 154 ans = input()
215 if len(ans) > 0 and ans.lower() not in ['y', 'yes']: 155 if len(ans) > 0 and ans.lower() not in ['y', 'yes']:
216 return 1 156 return 1
217 157
158 logger.debug("Deleting: %s" % theme_dir)
218 shutil.rmtree(theme_dir) 159 shutil.rmtree(theme_dir)
219 else:
220 logger.info("No currently active theme.")
221 160
161 return True
162
163 return False
164