Mercurial > piecrust2
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 |
