fe486c321d95b607f54068f9eb755b4e558b9a4d
[mdk.git] / mdk / commands / js.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 Moodle Development Kit
6
7 Copyright (c) 2014 Frédéric Massart - FMCorz.net
8
9 This program is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22 http://github.com/FMCorz/mdk
23 """
24
25 import logging
26 import os
27 import time
28 import datetime
29 import watchdog.events
30 import watchdog.observers
31 from ..command import Command
32 from .. import js, plugins
33
34
35 class JsCommand(Command):
36
37 _arguments = [
38 (
39 ['mode'],
40 {
41 'metavar': 'mode',
42 'help': 'the type of action to perform',
43 'sub-commands':
44 {
45 'shift': (
46 {
47 'help': 'keen to use shifter?'
48 },
49 [
50 (
51 ['-p', '--plugin'],
52 {
53 'action': 'store',
54 'dest': 'plugin',
55 'default': None,
56 'help': 'the name of the plugin or subsystem to target. If not passed, we do our best to guess from the current path.'
57 }
58 ),
59 (
60 ['-m', '--module'],
61 {
62 'action': 'store',
63 'dest': 'module',
64 'default': None,
65 'help': 'the name of the module in the plugin or subsystem. If omitted all the modules will be shifted, except we are in a module.'
66 }
67 ),
68 (
69 ['-w', '--watch'],
70 {
71 'action': 'store_true',
72 'dest': 'watch',
73 'help': 'watch for changes to re-shift'
74 }
75 ),
76 (
77 ['names'],
78 {
79 'default': None,
80 'help': 'name of the instances',
81 'metavar': 'names',
82 'nargs': '*'
83 }
84 )
85 ]
86 ),
87 'doc': (
88 {
89 'help': 'keen to generate documentation?'
90 },
91 [
92 (
93 ['names'],
94 {
95 'default': None,
96 'help': 'name of the instances',
97 'metavar': 'names',
98 'nargs': '*'
99 }
100 )
101 ]
102 )
103 }
104 }
105 )
106 ]
107 _description = 'Wrapper for JS functions'
108
109 def run(self, args):
110 if args.mode == 'shift':
111 self.shift(args)
112 elif args.mode == 'doc':
113 self.document(args)
114
115
116 def shift(self, args):
117 """The shift mode"""
118
119 Mlist = self.Wp.resolveMultiple(args.names)
120 if len(Mlist) < 1:
121 raise Exception('No instances to work on. Exiting...')
122
123 cwd = os.path.realpath(os.path.abspath(os.getcwd()))
124 mpath = Mlist[0].get('path')
125 relpath = cwd.replace(mpath, '').strip('/')
126
127 # TODO Put that logic somewhere else because it is going to be re-used, I'm sure.
128 if not args.plugin:
129 (subsystemOrPlugin, pluginName) = plugins.PluginManager.getSubsystemOrPluginFromPath(cwd, Mlist[0])
130 if subsystemOrPlugin:
131 args.plugin = subsystemOrPlugin + ('_' + pluginName) if pluginName else ''
132 logging.info("I guessed the plugin/subsystem to work on as '%s'" % (args.plugin))
133 else:
134 self.argumentError('The argument --plugin is required, I could not guess it.')
135
136 if not args.module:
137 candidate = relpath
138 module = None
139 while '/yui/src' in candidate:
140 (head, tail) = os.path.split(candidate)
141 if head.endswith('/yui/src'):
142 module = tail
143 break
144 candidate = head
145
146 if module:
147 args.module = module
148 logging.info("I guessed the JS module to work on as '%s'" % (args.module))
149
150 for M in Mlist:
151 if len(Mlist) > 1:
152 logging.info('Let\'s shift everything you wanted on \'%s\'' % (M.get('identifier')))
153
154 processor = js.Js(M)
155 processor.shift(subsystemOrPlugin=args.plugin, module=args.module)
156
157 if args.watch:
158 observer = watchdog.observers.Observer()
159
160 for M in Mlist:
161 processor = js.Js(M)
162 processorArgs = {'subsystemOrPlugin': args.plugin, 'module': args.module}
163 handler = JsShiftWatcher(M, processor, processorArgs)
164 observer.schedule(handler, processor.getYUISrcPath(**processorArgs), recursive=True)
165 logging.info('Watchdog set up on %s, waiting for changes...' % (M.get('identifier')))
166
167 observer.start()
168
169 try:
170 while True:
171 time.sleep(1)
172 except KeyboardInterrupt:
173 observer.stop()
174 finally:
175 observer.join()
176
177 def document(self, args):
178 """The docmentation mode"""
179
180 Mlist = self.Wp.resolveMultiple(args.names)
181 if len(Mlist) < 1:
182 raise Exception('No instances to work on. Exiting...')
183
184 for M in Mlist:
185 logging.info('Documenting everything you wanted on \'%s\'. This may take a while...', M.get('identifier'))
186 outdir = self.Wp.getExtraDir(M.get('identifier'), 'jsdoc')
187 outurl = self.Wp.getUrl(M.get('identifier'), extra='jsdoc')
188 processor = js.Js(M)
189 processor.document(outdir)
190 logging.info('Documentation available at:\n %s\n %s', outdir, outurl)
191
192
193 class JsShiftWatcher(watchdog.events.FileSystemEventHandler):
194
195 _processor = None
196 _args = None
197 _ext = ['.js', '.json']
198 _M = None
199
200 def __init__(self, M, processor, args):
201 super(self.__class__, self).__init__()
202 self._M = M
203 self._processor = processor
204 self._args = args
205
206 def on_modified(self, event):
207 self.process(event)
208
209 def process(self, event):
210 if event.is_directory:
211 return
212 elif not os.path.splitext(event.src_path)[1] in self._ext:
213 return
214
215 logging.info('[%s] (%s) Changes detected!' % (self._M.get('identifier'), datetime.datetime.now().strftime('%H:%M:%S')))
216
217 try:
218 self._processor.shift(**self._args)
219 except js.ShifterCompileFailed:
220 logging.error(' /!\ Error: Compile failed!')