diff options
author | Einar Egilsson | 2007-05-21 22:03:05 +0000 |
---|---|---|
committer | Einar Egilsson | 2007-05-21 22:03:05 +0000 |
commit | 502c24f18cdfa0f808d9383313bb4f965c7fb11f (patch) | |
tree | c9f2523995b6fc59e3bf6cc3bcc9200bbedd5e95 |
Redirector
git-svn-id: http://einaregilsson.googlecode.com/svn/mozilla/redirector/trunk@53 119bf307-c92d-0410-89bd-8f53e6181181
-rw-r--r-- | chrome.manifest | 5 | ||||
-rw-r--r-- | chrome/content/common.js | 37 | ||||
-rw-r--r-- | chrome/content/overlay.xul | 27 | ||||
-rw-r--r-- | chrome/content/redirect.js | 30 | ||||
-rw-r--r-- | chrome/content/redirect.xul | 39 | ||||
-rw-r--r-- | chrome/content/redirectList.js | 30 | ||||
-rw-r--r-- | chrome/content/redirectList.xul | 37 | ||||
-rw-r--r-- | chrome/content/redirector.js | 97 | ||||
-rw-r--r-- | chrome/content/redirector.png | bin | 0 -> 1462 bytes | |||
-rw-r--r-- | chrome/content/redirlib.js | 217 | ||||
-rw-r--r-- | chrome/locale/en-US/redirect.dtd | 8 | ||||
-rw-r--r-- | chrome/locale/en-US/redirectList.dtd | 9 | ||||
-rw-r--r-- | chrome/locale/en-US/redirector.dtd | 5 | ||||
-rw-r--r-- | chrome/locale/en-US/redirector.properties | 4 | ||||
-rw-r--r-- | chrome/skin/overlay.css | 1 | ||||
-rw-r--r-- | defaults/preferences/redirector.js | 6 | ||||
-rw-r--r-- | install.rdf | 23 | ||||
-rw-r--r-- | make.bat | 11 |
18 files changed, 586 insertions, 0 deletions
diff --git a/chrome.manifest b/chrome.manifest new file mode 100644 index 0000000..a64fc76 --- /dev/null +++ b/chrome.manifest @@ -0,0 +1,5 @@ +# $Id$ +content redirector file:chrome/content/ +locale redirector en-US file:chrome/locale/en-US/ +skin redirector classic/1.0 file:chrome/skin/ +overlay chrome://browser/content/browser.xul chrome://redirector/content/overlay.xul
\ No newline at end of file diff --git a/chrome/content/common.js b/chrome/content/common.js new file mode 100644 index 0000000..7d13e4b --- /dev/null +++ b/chrome/content/common.js @@ -0,0 +1,37 @@ + + +var RedirectorCommon = { + + wildcardMatch : function(pattern, text) { + var parts + , part + , i + , pos; + + parts = pattern.split('*'); + + for (i in parts) { + + part = parts[i]; + + pos = text.indexOf(part); + + if (pos == -1) { + return false; + } + + if (i == 0 && pos != 0) { + return false; + } + + if (i == parts.length -1 && i != "" && text.substr(text.length - part.length) != part) { + return false; + + } + + text = text.substr(pos + part.length); + } + + return true; + } +};
\ No newline at end of file diff --git a/chrome/content/overlay.xul b/chrome/content/overlay.xul new file mode 100644 index 0000000..7951516 --- /dev/null +++ b/chrome/content/overlay.xul @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- $Id$ --> +<?xml-stylesheet href="chrome://redirector/skin/overlay.css" type="text/css"?> +<!DOCTYPE overlay SYSTEM "chrome://redirector/locale/redirector.dtd"> +<overlay id="redirector-overlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="redirlib.js"/> + <script src="common.js"/> + <script src="redirector.js"/> + + <stringbundleset id="stringbundleset"> + <stringbundle id="redirector-strings" src="chrome://redirector/locale/redirector.properties"/> + </stringbundleset> + + <menupopup id="menu_ToolsPopup"> + <menuitem id="redirector-menuitem" label="&RedirectorMenuItem.label;" + accesskey="&RedirectorMenuItem.accesskey;" + oncommand="Redirector.onMenuItemCommand(event);"/> + </menupopup> + <popup id="contentAreaContextMenu"> + <menuitem id="redirector-context" label="&RedirectorContext.label;" + accesskey="&RedirectorContext.accesskey;" + insertafter="context-stop" + oncommand="Redirector.onContextMenuCommand(event)"/> + </popup> +</overlay>
\ No newline at end of file diff --git a/chrome/content/redirect.js b/chrome/content/redirect.js new file mode 100644 index 0000000..bd1a686 --- /dev/null +++ b/chrome/content/redirect.js @@ -0,0 +1,30 @@ +//// $Id$ + +var Redirect = { + + onLoad : function() { + var params = window.arguments[0]; + $('txtExampleUrl').value = params.inn.url; + $('txtPattern').value = params.inn.url; + $('txtRedirectUrl').value = params.inn.redirect || ''; + + }, + + onAccept : function() { + var params = window.arguments[0]; + + params.out.pattern = $('txtPattern').value; + params.out.patternType = 'Wildcard'; + params.out.redirectUrl = $('txtRedirectUrl').value; + params.out.onlyIfLinkExists = $('chkOnlyIfLinkExists').checked; + + return true; + }, + + testPattern : function() { + try { + alert(RedirectorCommon.wildcardMatch($('txtPattern').value, $('txtExampleUrl').value)); + } catch(e) {alert(e);} + } + +};
\ No newline at end of file diff --git a/chrome/content/redirect.xul b/chrome/content/redirect.xul new file mode 100644 index 0000000..ea8ac02 --- /dev/null +++ b/chrome/content/redirect.xul @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<!DOCTYPE dialog SYSTEM "chrome://redirector/locale/redirect.dtd">
+<dialog title="&redirectWindow.title;"
+ orient="vertical"
+ autostretch="always"
+ onload="Redirect.onLoad();"
+ buttons="accept,cancel"
+ ondialogaccept="return Redirect.onAccept();"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/x-javascript" src="common.js"/>
+ <script type="application/x-javascript" src="redirlib.js"/>
+ <script type="application/x-javascript" src="redirect.js"/>
+ <style type="text/css">
+ textbox { width:500px; }
+ </style>
+
+ <grid>
+ <rows>
+ <row>
+ <text value="&txtExampleUrl.label;"/>
+ <textbox id="txtExampleUrl" disabled="true" />
+ </row>
+ <row>
+ <text value="&txtPattern.label;"/>
+ <textbox id="txtPattern"/>
+ <button id="btnTestPattern" label="&btnTestPattern.label;" onclick="Redirect.testPattern();" />
+ </row>
+ <row>
+ <text value="&txtRedirectUrl.label;"/>
+ <textbox id="txtRedirectUrl"/>
+ </row>
+ </rows>
+ </grid>
+ <checkbox id="chkOnlyIfLinkExists" label="&chkOnlyIfLinkExists.label;"/>
+
+</dialog>
diff --git a/chrome/content/redirectList.js b/chrome/content/redirectList.js new file mode 100644 index 0000000..522fa20 --- /dev/null +++ b/chrome/content/redirectList.js @@ -0,0 +1,30 @@ +var RedirectList = { + + + addItemsToListBox : function(items) { + + var list = document.getElementById('foo'); + var item, row, value; + + for each (item in items) { + + row = document.createElement('listitem'); + + for each (value in item) { + cell = document.createElement('listcell'); + cell.setAttribute('label',value); + cell.setAttribute('value',value); + row.appendChild(cell); + } + + list.appendChild(row); + } + }, + + + + onLoad : function() { + addItemsToList(items); + } + +}; diff --git a/chrome/content/redirectList.xul b/chrome/content/redirectList.xul new file mode 100644 index 0000000..163703e --- /dev/null +++ b/chrome/content/redirectList.xul @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<!DOCTYPE dialog SYSTEM "chrome://redirector/locale/redirectList.dtd">
+<dialog title="&window.title;"
+ orient="vertical"
+ autostretch="always"
+ onload="RedirectList.onLoad();"
+ buttons="accept"
+ ondialogaccept="return RedirectList.onAccept();"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/x-javascript" src="common.js"/>
+ <script type="application/x-javascript" src="redirlib.js"/>
+ <script type="application/x-javascript" src="redirectList.js"/>
+
+ <dialogheader title="&header;" description="&header.description;"/>
+
+ <listbox id="foo">
+
+ <listhead>
+ <listheader label="&colPattern.label;"/>
+ <listheader label="&colRedirectTo.label;"/>
+ <listheader label="&colOnlyIfLinkExits.label;"/>
+ <listheader label="&colPatternType.label;"/>
+ </listhead>
+
+ <listcols>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+
+ </listcols>
+
+ </listbox>
+</dialog>
diff --git a/chrome/content/redirector.js b/chrome/content/redirector.js new file mode 100644 index 0000000..6767840 --- /dev/null +++ b/chrome/content/redirector.js @@ -0,0 +1,97 @@ +//// $Id$ + +var Redirector = { + + id : "redirector@einaregilsson.com", + name : "Redirector", + initialized : false, + strings : null, + redirects : [], + + onLoad : function(event) { + try { + + // initialization code + RedirLib.initialize(this); + RedirLib.debug("Initializing..."); + + $('contentAreaContextMenu') + .addEventListener("popupshowing", function(e) { Redirector.showContextMenu(e); }, false); + var appcontent = window.document.getElementById('appcontent'); + + if (appcontent && !appcontent.processed) { + appcontent.processed = true; + + appcontent.addEventListener('DOMContentLoaded', function(event) { + + Redirector.onDOMContentLoaded(event); + + }, false); + } + this.strings = document.getElementById("redirector-strings"); + + RedirLib.debug("Finished initialization"); + this.initialized = true; + + } catch(e) { + //Don't use RedirLib because it's initialization might have failed. + if (this.strings) { + alert(this.strings.getString("initError")._(this.name) + "\n\n" + e); + } else { + alert(e); + } + } + }, + + onDOMContentLoaded : function(event) { + var redirect; + try { + for each (redirect in this.redirects) { + if (RedirectorCommon.wildcardMatch(redirect.pattern, window.content.location.href)) { + window.content.location.href = redirect.redirectUrl; + } + } + } catch(e) {alert(e);} + + }, + + onUnload : function(event) { + //Clean up here + RedirLib.debug("Finished cleanup"); + }, + + showContextMenu : function(event) { + if (gContextMenu.onLink) { + $("redirector-context").label = this.strings.getString('addLinkUrl'); + } else { + $("redirector-context").label = this.strings.getString('addCurrentUrl'); + } + }, + + onContextMenuCommand: function(event) { + + params = { inn : { url : window.content.location.href}, out : {} }; + if (gContextMenu.onLink) { + params.inn.redirect = gContextMenu.link.toString(); + } + + window.openDialog("chrome://redirector/content/redirect.xul", + "redirect", + "chrome,dialog,modal,centerscreen", params); + + if (params.out.pattern) { + this.redirects.push(params.out); + } + }, + + onMenuItemCommand: function(event) { + window.openDialog("chrome://redirector/content/redirectList.xul", + "redirectList", + "chrome,dialog,modal,centerscreen", this); + + }, + +}; + +window.addEventListener("load", function(event) { Redirector.onLoad(event); }, false); +window.addEventListener("unload", function(event) { Redirector.onUnload(event); }, false);
\ No newline at end of file diff --git a/chrome/content/redirector.png b/chrome/content/redirector.png Binary files differnew file mode 100644 index 0000000..f8de12c --- /dev/null +++ b/chrome/content/redirector.png diff --git a/chrome/content/redirlib.js b/chrome/content/redirlib.js new file mode 100644 index 0000000..493c583 --- /dev/null +++ b/chrome/content/redirlib.js @@ -0,0 +1,217 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ *
+ * RedirLib - Utility functions for Firefox extensions
+ *
+ * Einar Egilsson
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+var RedirLib = {
+
+ _debug : false,
+ _ext : null,
+ _cout : null,
+ _prefBranch : null,
+ version : 0.2,
+
+ initialize : function(extension) {
+ this._ext = extension;
+ this._cout = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
+ this._debug = true;
+ this._initPrefs();
+ },
+
+ debug : function(str) {
+ if (this._ext.prefs.debug) {
+ this._cout.logStringMessage("%1: %2"._(this._ext.name, str));
+ }
+ },
+
+
+ debugObject : function(name, obj) {
+ s = name + ': ';
+ for (x in obj)
+ s += "\n\t%1 : %2"._(x, obj[x]);
+ this.debug(s);
+ },
+
+ //Adds all prefs to a prefs object on the extension object, and registers a pref observer
+ //for the branch.
+ _initPrefs : function() {
+
+ this._prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService)
+ .getBranch("extensions.%1."._(this._ext.id.split("@")[0]));
+ this._ext.prefs = {};
+
+ var list = this._prefBranch.getChildList("", {}).toString().split(",");
+
+ for (i in list) {
+
+ var name = list[i];
+
+ var type = this._prefBranch.getPrefType(name);
+
+ if (type == this._prefBranch.PREF_STRING) {
+ this._ext.prefs[name] = this._prefBranch.getCharPref(name);
+ } else if (type == this._prefBranch.PREF_INT) {
+ this._ext.prefs[name] = this._prefBranch.getIntPref(name);
+ } else if (type == this._prefBranch.PREF_BOOL) {
+ this._ext.prefs[name] = this._prefBranch.getBoolPref(name);
+ }
+ }
+ },
+
+
+ getCharPref : function(branch) {
+ return this._prefBranch.getCharPref(branch);
+ },
+
+ getBoolPref : function(branch) {
+ return this._prefBranch.getBoolPref(branch);
+ },
+
+ getIntPref : function(branch) {
+ return this._prefBranch.getIntPref(branch);
+ },
+
+ getExtensionFolder : function() {
+ return Cc["@mozilla.org/extensions/manager;1"]
+ .getService(Ci.nsIExtensionManager)
+ .getInstallLocation(this._ext.id)
+ .getItemLocation(this._ext.id);
+
+ },
+
+ getEnvVar : function(name) {
+ return Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment)
+ .get(name);
+
+ },
+
+ setEnvVar : function(name, value) {
+ return Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment)
+ .set(name, value);
+
+ },
+
+ msgBox : function(title, text) {
+ Cc["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Ci.nsIPromptService)
+ .alert(window, title, text);
+ },
+
+ //Converts a chrome path to a local file path. Note that the
+ //file specified at the end of the chrome path does not have
+ //to exist.
+ chromeToPath : function(path) {
+ var rv;
+ var ios = Cc["@mozilla.org/network/io-service;1"].createInstance(Ci.nsIIOService);
+ var uri = ios.newURI(path, 'UTF-8', null);
+ var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].createInstance(Ci.nsIChromeRegistry);
+ return cr.convertChromeURL(uri);
+ return decodeURI(rv.spec.substr("file:///".length).replace(/\//g, "\\"));
+ },
+
+ //Saves 'content' to file 'filepath'. Note that filepath needs to
+ //be a real file path, not a chrome path.
+ saveToFile : function(filepath, content) {
+ var file = this.getFile(filepath);
+
+ if (!file.exists()) {
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 420);
+ }
+ var outputStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+
+ outputStream.init( file, 0x04 | 0x08 | 0x20, 420, 0 );
+ var result = outputStream.write( content, content.length );
+ outputStream.close();
+ },
+
+ startProcess: function(filename, args) {
+
+ var file = this.getFile(filename);
+
+ args = args ? args : [];
+
+ if (file.exists()) {
+ var nsIProcess = Cc["@mozilla.org/process/util;1"].getService(Ci.nsIProcess);
+ nsIProcess.init(file);
+ nsIProcess.run(false, args, args.length);
+ } else {
+ throw Error("File '%1' does not exist!"._(filename));
+ }
+
+ },
+
+ //Simulates a double click on the file in question
+ launchFile : function(filepath) {
+ var f = this.getFile(filepath);
+ f.launch();
+ },
+
+
+ //Gets a local file reference, return the interface nsILocalFile.
+ getFile : function(filepath) {
+ var f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ f.initWithPath(filepath);
+ return f;
+ },
+
+ //Returns all elements that match the query sent in. The root used
+ //in the query is the window.content.document, so this will only
+ //work for html content.
+ xpath : function(doc, query) {
+ return doc.evaluate(query, doc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
+ }
+
+
+
+};
+
+//************** Some prototype enhancements ************** //
+
+if (!window.Cc) window.Cc = Components.classes;
+if (!window.Ci) window.Ci = Components.interfaces;
+
+//Returns true if the string contains the substring str.
+String.prototype.contains = function (str) {
+ return this.indexOf(str) != -1;
+};
+
+String.prototype.trim = function() {
+ return this.replace(/^\s*|\s*$/gi, '');
+};
+
+String.prototype.startsWith = function(s) {
+ return (this.length >= s.length && this.substr(0, s.length) == s);
+};
+
+String.prototype.endsWith = function(s) {
+ return (this.length >= s.length && this.substr(this.length - s.length) == s);
+};
+
+//Inserts the arguments into the string. E.g. if
+//the string is "Hello %1" then "Hello %1"._('johnny')
+//would return "Hello johnny"
+String.prototype._ = function() {
+ s = this;
+ for (var i = 0; i < arguments.length; i++) {
+ var nr = "%" + (i+1);
+ var repl;
+ if (arguments[i] == null) {
+ repl = "null";
+ } else if (arguments[i] == undefined) {
+ repl = "undefined";
+ } else {
+ repl = arguments[i];
+ }
+ s = s.replace(new RegExp(nr, "g"), repl);
+ }
+ return s;
+};
+
+function $(id) {
+ return document.getElementById(id);
+}
\ No newline at end of file diff --git a/chrome/locale/en-US/redirect.dtd b/chrome/locale/en-US/redirect.dtd new file mode 100644 index 0000000..3014c52 --- /dev/null +++ b/chrome/locale/en-US/redirect.dtd @@ -0,0 +1,8 @@ +<!-- $Id$ --> +<!ENTITY redirectWindow.title "Redirect"> +<!ENTITY txtExampleUrl.label "Example url"> +<!ENTITY txtPattern.label "Pattern"> +<!ENTITY txtRedirectUrl.label "Redirect to"> +<!ENTITY chkOnlyIfLinkExists.label "Only if link exists"> +<!ENTITY btnTestPattern.label "Test pattern"> + diff --git a/chrome/locale/en-US/redirectList.dtd b/chrome/locale/en-US/redirectList.dtd new file mode 100644 index 0000000..7436cb7 --- /dev/null +++ b/chrome/locale/en-US/redirectList.dtd @@ -0,0 +1,9 @@ +<!-- $Id$ --> +<!ENTITY window.title "Redirects"> +<!ENTITY header "Pattern"> +<!ENTITY colPattern.label "Pattern"> +<!ENTITY colRedirectTo.label "Redirect to"> +<!ENTITY colOnlyIfLinkExits.label "Only if link exists"> +<!ENTITY colPatternType.label "Pattern type"> +<!ENTITY header "Redirects"> +<!ENTITY header.description "All active redirects"> diff --git a/chrome/locale/en-US/redirector.dtd b/chrome/locale/en-US/redirector.dtd new file mode 100644 index 0000000..3755f33 --- /dev/null +++ b/chrome/locale/en-US/redirector.dtd @@ -0,0 +1,5 @@ +<!-- $Id$ --> +<!ENTITY RedirectorMenuItem.label "Redirector"> +<!ENTITY RedirectorMenuItem.accesskey "R"> +<!ENTITY RedirectorContext.label "Add url to Redirector..."> +<!ENTITY RedirectorContext.accesskey "A">
\ No newline at end of file diff --git a/chrome/locale/en-US/redirector.properties b/chrome/locale/en-US/redirector.properties new file mode 100644 index 0000000..110f82c --- /dev/null +++ b/chrome/locale/en-US/redirector.properties @@ -0,0 +1,4 @@ +initError=Failed to initialize %1. +extensions.redirector@einaregilsson.com.description=Automatically redirects to user-defined urls on certain pages +addCurrentUrl=Add current url to Redirector +addLinkUrl=Add link url to Redirector
\ No newline at end of file diff --git a/chrome/skin/overlay.css b/chrome/skin/overlay.css new file mode 100644 index 0000000..b670fa5 --- /dev/null +++ b/chrome/skin/overlay.css @@ -0,0 +1 @@ +/* Include your overlay stylings here */
\ No newline at end of file diff --git a/defaults/preferences/redirector.js b/defaults/preferences/redirector.js new file mode 100644 index 0000000..d7985a9 --- /dev/null +++ b/defaults/preferences/redirector.js @@ -0,0 +1,6 @@ +//// $Id$ + +pref("extensions.redirector.debug", false); + +// See http://kb.mozillazine.org/Localize_extension_descriptions +pref("extensions.redirector@einaregilsson.com.description", "chrome://redirector/locale/redirector.properties");
\ No newline at end of file diff --git a/install.rdf b/install.rdf new file mode 100644 index 0000000..7dabe48 --- /dev/null +++ b/install.rdf @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- $Id$ --> +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + <Description about="urn:mozilla:install-manifest"> + <em:id>redirector@einaregilsson.com</em:id> + <em:name>Redirector</em:name> + <em:version>0.1</em:version> + <em:creator>Einar Egilsson</em:creator> + <em:description>Automatically redirects to user-defined urls on certain pages</em:description> + <!-- <em:homepageURL>http://addons.mozilla.org/{app}/xxx/</em:homepageURL> --> <!-- Add after upload to addons.mozilla.org --> + <em:aboutURL>chrome://redirector/content/about.xul</em:aboutURL> + <em:optionsURL>chrome://redirector/content/options.xul</em:optionsURL> + <em:iconURL>chrome://redirector/content/redirector.png</em:iconURL> + <em:targetApplication> + <Description> + <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!-- firefox --> + <em:minVersion>2.0</em:minVersion> + <em:maxVersion>2.0.0.*</em:maxVersion> + </Description> + </em:targetApplication> + </Description> +</RDF>
\ No newline at end of file diff --git a/make.bat b/make.bat new file mode 100644 index 0000000..e4868af --- /dev/null +++ b/make.bat @@ -0,0 +1,11 @@ +:: +:: Build script for extension +:: +:: Einar Þór Egilsson - 26.10.2006 +:: + +SET EXT=Redirector.xpi + +IF NOT EXIST build md build +IF EXIST build\%EXT% del build\%EXT% +zip -r build\%EXT% *.* -x build* *.py *.bat
\ No newline at end of file |