1+ from sys import argv
2+ from os import path , getcwd
3+ from watchgod import watch
4+ from watchgod .watcher import DefaultDirWatcher
5+ from json import loads as load_json , dumps as dump_json
6+ from uuid import uuid4
7+ from traceback import format_exc as get_traceback
8+ from .version import VERSION_STRING
9+ from jsmin import jsmin
10+ from sass import compile as sass_compile
11+ import click
12+ import re
13+
14+ class ValidationError (Exception ):
15+ def __init__ (self , message ):
16+ super ().__init__ (message )
17+
18+ class EleranWatcher (DefaultDirWatcher ):
19+ def should_watch_file (self , entry ):
20+ return entry .name .endswith (('.scss' , '.js' , '.sass' , '.json' ))
21+
22+ class Eleran ():
23+ def __init__ (self , TargetDir = None , Mode = None ):
24+ self .BasePath = getcwd ()
25+ self .WatchFiles = []
26+ self .ConfigFile = "eleran.json"
27+ self .Config = {}
28+ self .Index = {}
29+ self .Version = VERSION_STRING
30+ self .Mode = Mode
31+
32+ # Path
33+ if TargetDir :
34+ self .BasePath = path .join (getcwd (), TargetDir )
35+
36+ # Print
37+ echo_click (" * Eleran version" , self .Version )
38+
39+ def read_file (self , Filename , Mode = "r" ):
40+ FileObject = open (Filename , Mode )
41+ String = FileObject .read ()
42+ return String
43+
44+ def get_id (self , Filepath ):
45+ ID = self .Index .get (Filepath )
46+ return ID
47+
48+ def get_sass_imported (self , Source , ID ):
49+ String = self .read_file (Source )
50+ Result = re .findall ('^@import\s*".+"' , String , re .MULTILINE | re .IGNORECASE )
51+ for Item in Result :
52+ Filename = Item .replace (" " , "" )
53+ Filename = Filename .replace ('@import"' , "" )
54+ Filename = Filename [:- 1 ]
55+ Filename = "_" + Filename + ".scss"
56+ Filepath = path .join (path .dirname (Source ), Filename )
57+ if path .isfile (Filepath ):
58+ if not Filepath in self .WatchFiles :
59+ self .Index [Filepath ] = ID
60+ self .WatchFiles .append (Filepath )
61+
62+ def watch (self ):
63+ # Print
64+ echo_click (" * Watch for" , self .BasePath , Color = "green" )
65+ echo_click (" * Press CTRL+C to quit" , Color = "green" )
66+
67+ # Config File
68+ ConfigFile = path .join (self .BasePath , self .ConfigFile )
69+
70+ # Watch
71+ for Changes in watch (self .BasePath , watcher_cls = EleranWatcher ):
72+ _Type , FileChange = list (Changes )[0 ]
73+ TypeChange = _Type .name .capitalize ()
74+ if FileChange in self .WatchFiles :
75+ echo_click (" *" , TypeChange , FileChange )
76+ ID = self .get_id (FileChange )
77+ if self .Mode == "debug" :
78+ echo_click (" * Build ID:" , ID )
79+ # Build
80+ self .build (ID )
81+ elif FileChange == ConfigFile :
82+ echo_click (" *" , TypeChange , FileChange )
83+ self .reload_config ()
84+
85+ def build (self , ID ):
86+ try :
87+ ConfigData = self .Config .get (ID )
88+ ConfigType = ConfigData .get ("Type" )
89+
90+ if ConfigType == "sass" :
91+ SassSource = ConfigData .get ("source" )
92+ SassOutput = ConfigData .get ("output" )
93+ SassStyle = ConfigData .get ("output_style" )
94+ SassComment = ConfigData .get ("source_comments" )
95+ Filepath = path .join (self .BasePath , SassSource )
96+ String = sass_compile (
97+ filename = Filepath ,
98+ output_style = SassStyle ,
99+ source_comments = SassComment
100+ )
101+
102+ # Detect new import
103+ self .get_sass_imported (Filepath , ID )
104+
105+ # Save
106+ FileWrite = open (path .join (self .BasePath , SassOutput ), "w" )
107+ FileWrite .write (String )
108+ FileWrite .close ()
109+
110+ elif ConfigType == "js" :
111+ JSInclude = ConfigData .get ("include" )
112+ JSOutput = ConfigData .get ("output" )
113+ JSString = ""
114+
115+ # Join
116+ for i in JSInclude :
117+ Filepath = path .join (self .BasePath , i )
118+ String = self .read_file (Filepath )
119+ JSString += String
120+
121+ # Save
122+ JSString = jsmin (JSString )
123+ FileWrite = open (path .join (self .BasePath , JSOutput ), "w" )
124+ FileWrite .write (JSString )
125+ FileWrite .close ()
126+
127+ except Exception as e :
128+ echo_click (" * Error:" , e , Color = "red" )
129+
130+ def generate_config (self ):
131+ Sample = [
132+ {
133+ "sass" : {
134+ "source" : "sass/style.scss" ,
135+ "output" : "style.min.css" ,
136+ "output_style" : "compressed" ,
137+ "source_comments" : False
138+ }
139+ },
140+ {
141+ "js" : {
142+ "include" : [
143+ "js/fo.js" ,
144+ "js/bar.js"
145+ ],
146+ "output" : "script.min.js"
147+ }
148+ }
149+ ]
150+
151+ # Save
152+ String = dump_json (Sample , indent = "\t " )
153+ Filepath = path .join (self .BasePath , "eleran.json" )
154+
155+ # Print
156+ echo_click (" * Creating config file to" , Filepath )
157+
158+ # Save
159+ FileWrite = open (Filepath , "w" )
160+ FileWrite .write (String )
161+ FileWrite .close ()
162+
163+ def reload_config (self ):
164+ self .Index = {}
165+ self .WatchFiles = []
166+ self .Config = {}
167+ self .load_config ()
168+
169+ def load_config (self ):
170+ # Config
171+ ConfigFile = path .join (self .BasePath , self .ConfigFile )
172+
173+ # Print
174+ echo_click (" * Loading config:" , ConfigFile )
175+
176+ # Is file exist
177+ if path .isfile (ConfigFile ):
178+ # Load file
179+ Config = self .read_file (ConfigFile )
180+ Config = load_json (Config )
181+
182+ for Item in Config :
183+ SassConfig = Item .get ("sass" )
184+ JSConfig = Item .get ("js" )
185+ ID = str (uuid4 ())
186+ if SassConfig :
187+ # Config
188+ SassConfig ["Type" ] = "sass"
189+ self .Config [ID ] = SassConfig
190+
191+ # File
192+ File = SassConfig .get ("source" )
193+ if File :
194+ Filepath = path .join (self .BasePath , File )
195+ if path .isfile (Filepath ):
196+ self .get_sass_imported (Filepath , ID )
197+ self .Index [Filepath ] = ID
198+ self .WatchFiles .append (Filepath )
199+
200+ else :
201+ echo_click (" *" , File , "not found" , Color = "red" )
202+ else :
203+ echo_click (" * Sass source file not found" , Color = "red" )
204+
205+ elif JSConfig :
206+ # Config
207+ JSConfig ["Type" ] = "js"
208+ self .Config [ID ] = JSConfig
209+
210+ # Files
211+ Files = JSConfig .get ("include" )
212+ if Files :
213+ for File in Files :
214+ Filepath = path .join (self .BasePath , File )
215+ if path .isfile (Filepath ):
216+ self .Index [Filepath ] = ID
217+ self .WatchFiles .append (Filepath )
218+ else :
219+ echo_click (" *" , File , "not found" , Color = "red" )
220+ else :
221+ echo_click (" * JS include files not found" , Color = "red" )
222+ else :
223+ echo_click (" * Unknown config type" , Color = "red" )
224+ else :
225+ raise ValidationError ("Config file not found" )
226+
227+ def echo_click (* args , Color = None ):
228+ Text = []
229+ for i in args :
230+ Text .append (str (i ))
231+ Text = " " .join (Text )
232+ if Color :
233+ click .echo (click .style (Text , fg = Color ))
234+ else :
235+ click .echo (Text )
236+
237+ @click .command ()
238+ @click .argument ('command' , default = 'watch' )
239+ @click .argument ('target' , default = '' )
240+ @click .option ('--mode' , default = '' )
241+ def cli (command , target , mode ):
242+ try :
243+ # App
244+ App = Eleran (target , mode )
245+
246+ # Command
247+ if command == "watch" :
248+ App .load_config ()
249+ App .watch ()
250+ elif command == "validate" :
251+ App .load_config ()
252+ echo_click (" * Config validation success!" , Color = "green" )
253+ elif command == "generate" :
254+ App .generate_config ()
255+ echo_click (" * Success!" , Color = "green" )
256+
257+ except KeyboardInterrupt :
258+ print (" * Exit" )
259+
260+ except Exception as e :
261+ if mode == "debug" :
262+ echo_click (" * " + str (get_traceback ()), Color = "red" )
263+ else :
264+ echo_click (" * Error: " + str (e ), Color = "red" )
0 commit comments