Kapileshwar Singh | 273de83 | 2015-08-20 18:15:29 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright 2015-2015 ARM Limited |
| 3 | # |
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | # you may not use this file except in compliance with the License. |
| 6 | # You may obtain a copy of the License at |
| 7 | # |
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | # |
| 10 | # Unless required by applicable law or agreed to in writing, software |
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | # See the License for the specific language governing permissions and |
| 14 | # limitations under the License. |
| 15 | # |
| 16 | |
| 17 | |
| 18 | """This is a script to publish a notebook containing Ipython graphs |
| 19 | The static data is published as an anonymous gist. GitHub does not |
| 20 | allow easy deletions of anonymous gists. |
| 21 | """ |
| 22 | |
| 23 | import json |
| 24 | import os |
| 25 | import re |
| 26 | import requests |
| 27 | import argparse |
| 28 | import urlparse |
| 29 | from IPython.nbformat.sign import TrustNotebookApp |
| 30 | from requests.auth import HTTPBasicAuth |
| 31 | from argparse import RawTextHelpFormatter |
| 32 | from ConfigParser import ConfigParser |
| 33 | |
| 34 | # Logging Configuration |
| 35 | import logging |
| 36 | from trappy.plotter import IPythonConf |
| 37 | |
| 38 | logging.basicConfig(level=logging.INFO) |
| 39 | |
| 40 | RAWGIT = "rawgit.com" |
| 41 | GITHUB_API_URL = "https://api.github.com/gists" |
| 42 | |
| 43 | |
| 44 | def change_resource_paths(txt): |
| 45 | """Change the resource paths from local to |
| 46 | Web URLs |
| 47 | """ |
| 48 | |
| 49 | # Replace the path for d3-tip |
| 50 | txt = txt.replace( |
| 51 | IPythonConf.add_web_base("plotter_scripts/EventPlot/d3.tip.v0.6.3"), |
| 52 | "http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3") |
| 53 | txt = txt.replace( |
| 54 | IPythonConf.add_web_base("plotter_scripts/EventPlot/d3.v3.min"), |
| 55 | "http://d3js.org/d3.v3.min") |
| 56 | txt = txt.replace( |
| 57 | IPythonConf.add_web_base("plotter_scripts/EventPlot/EventPlot"), |
| 58 | "https://rawgit.com/sinkap/7f89de3e558856b81f10/raw/46144f8f8c5da670c54f826f0c634762107afc66/EventPlot") |
| 59 | txt = txt.replace( |
| 60 | IPythonConf.add_web_base("plotter_scripts/ILinePlot/synchronizer"), |
| 61 | "http://dygraphs.com/extras/synchronizer") |
| 62 | txt = txt.replace( |
| 63 | IPythonConf.add_web_base("plotter_scripts/ILinePlot/dygraph-combined"), |
| 64 | "http://cdnjs.cloudflare.com/ajax/libs/dygraph/1.1.1/dygraph-combined") |
| 65 | txt = txt.replace( |
| 66 | IPythonConf.add_web_base("plotter_scripts/ILinePlot/ILinePlot"), |
| 67 | "https://rawgit.com/sinkap/648927dfd6985d4540a9/raw/69d6f1f9031ae3624c15707315ce04be1a9d1ac3/ILinePlot") |
Kapileshwar Singh | 324d336 | 2015-09-03 12:37:52 +0100 | [diff] [blame^] | 68 | txt = txt.replace( |
| 69 | IPythonConf.add_web_base("plotter_scripts/ILinePlot/underscore-min"), |
| 70 | "https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min") |
Kapileshwar Singh | 273de83 | 2015-08-20 18:15:29 -0700 | [diff] [blame] | 71 | |
| 72 | logging.info("Updated Library Paths...") |
| 73 | return txt |
| 74 | |
| 75 | |
| 76 | def get_url_from_response(response, file_name): |
| 77 | """Get the URL of gist from GitHub API response""" |
| 78 | |
| 79 | resp_data = response.json() |
| 80 | url = resp_data["files"][file_name]["raw_url"] |
| 81 | url = list(urlparse.urlsplit(url)) |
| 82 | url[1] = RAWGIT |
| 83 | url = urlparse.urlunsplit(url) |
| 84 | |
| 85 | logging.info("gist created at: %s", url) |
| 86 | return url |
| 87 | |
| 88 | |
| 89 | def fig_to_json(fig, profile): |
| 90 | """Get the underlying data file from figure name""" |
| 91 | |
| 92 | data_dir = IPythonConf.get_data_path(profile) |
| 93 | |
| 94 | return os.path.expanduser( |
| 95 | os.path.join( |
| 96 | data_dir, |
| 97 | fig + |
| 98 | ".json")) |
| 99 | |
| 100 | |
| 101 | def create_new_gist(fig, profile, login): |
| 102 | """Create a new gist for the data of the figure""" |
| 103 | |
| 104 | path = fig_to_json(fig, profile) |
| 105 | file_name = os.path.basename(path) |
| 106 | |
| 107 | with open(path) as file_h: |
| 108 | content = file_h.read() |
| 109 | |
| 110 | data = {} |
| 111 | data["description"] = "Gist Data: {}".format(file_name) |
| 112 | data["public"] = True |
| 113 | data["files"] = {} |
| 114 | data["files"][file_name] = {} |
| 115 | data["files"][file_name]["content"] = content |
| 116 | response = requests.post(GITHUB_API_URL, data=json.dumps(data), auth=login) |
| 117 | return get_url_from_response(response, file_name) |
| 118 | |
| 119 | |
| 120 | def publish(source, target, profile, login): |
| 121 | """Publish the notebook for globally viewable interactive |
| 122 | plots |
| 123 | """ |
| 124 | |
| 125 | regex = r"(ILinePlot|EventPlot)\.generate\(\'(fig_.{32})\', '\/(nbextensions|static)\/'\)" |
| 126 | txt = "" |
| 127 | |
| 128 | with open(source, 'r') as file_fh: |
| 129 | |
| 130 | for line in file_fh: |
| 131 | match = re.search(regex, line) |
| 132 | if match: |
| 133 | plot = match.group(1) |
| 134 | fig = match.group(2) |
| 135 | logging.info("Publishing %s : %s", plot, fig) |
| 136 | line = re.sub( |
| 137 | regex, |
| 138 | plot + ".generate('" + fig + "', '" + |
| 139 | create_new_gist(fig, profile, login) + "')", |
| 140 | line) |
| 141 | txt += line |
| 142 | |
| 143 | txt = change_resource_paths(txt) |
| 144 | |
| 145 | with open(target, 'w') as file_fh: |
| 146 | file_fh.write(txt) |
| 147 | |
| 148 | trust = TrustNotebookApp() |
| 149 | trust.sign_notebook(target) |
| 150 | logging.info("Signed and Saved: %s", target) |
| 151 | |
| 152 | def read_login_config(config_file): |
| 153 | """returns an HTTPBasicAuth object if the |
| 154 | config exists""" |
| 155 | |
| 156 | if not config_file: |
| 157 | logging.debug("Anonymous gists will be created") |
| 158 | return None |
| 159 | |
| 160 | with open(config_file, 'r') as c_fh: |
| 161 | config = ConfigParser() |
| 162 | config.readfp(c_fh) |
| 163 | username = config.get("login", "username") |
| 164 | token = config.get("login", "token") |
| 165 | |
| 166 | logging.info("Received Login info for: %s", username) |
| 167 | |
| 168 | return HTTPBasicAuth(username, token) |
| 169 | |
| 170 | def main(): |
| 171 | """Command Line Invocation Routine""" |
| 172 | |
| 173 | parser = argparse.ArgumentParser(description=""" |
| 174 | The data for the interactive plots is stored in the ipython profile. |
| 175 | In order to make it accessible when the notebook is published or shared, |
| 176 | a github gist of the data is created and the links in the notebook are |
| 177 | updated. The library links are also updated to their corresponding publicly |
| 178 | accessible URLs. |
| 179 | |
| 180 | The login credentials can be added to a config file as follows |
| 181 | |
| 182 | 1. Go to settings in your github profile and create a 'Personal Access Token' |
| 183 | 2. This token can be used in place of your password for BasicAuth APIs |
| 184 | 3. Create a config file: |
| 185 | |
| 186 | [login] |
| 187 | username=<your github username> |
| 188 | token=<personal access token> |
| 189 | |
| 190 | and pass the path to the file as -c <config>. |
| 191 | The gists can then be viewed in the corresponding github account. |
| 192 | |
| 193 | The absence of this will create an anonymous gist which cannot be deleted/managed.""", |
| 194 | prog="publish_interactive_plots.py", formatter_class=RawTextHelpFormatter) |
| 195 | |
| 196 | parser.add_argument( |
| 197 | "-p", |
| 198 | "--profile", |
| 199 | help="ipython profile", |
| 200 | default="default", |
| 201 | type=str) |
| 202 | |
| 203 | parser.add_argument( |
| 204 | "-o", |
| 205 | "--outfile", |
| 206 | help="name of the output notebook", |
| 207 | default="", |
| 208 | type=str) |
| 209 | |
| 210 | parser.add_argument( |
| 211 | "-c", |
| 212 | "--config", |
| 213 | help="The path to a config file containing github login credentials", |
| 214 | default=None, |
| 215 | type=str) |
| 216 | |
| 217 | parser.add_argument("notebook") |
| 218 | args = parser.parse_args() |
| 219 | |
| 220 | profile = args.profile |
| 221 | notebook = args.notebook |
| 222 | outfile = args.outfile |
| 223 | config = args.config |
| 224 | login = read_login_config(config) |
| 225 | |
| 226 | if outfile == "": |
| 227 | outfile = "published_" + os.path.basename(notebook) |
| 228 | logging.info("Setting outfile as %s", outfile) |
| 229 | |
| 230 | elif not outfile.endswith(".ipynb"): |
| 231 | outfile += ".ipynb" |
| 232 | |
| 233 | publish(notebook, outfile, profile, login) |
| 234 | |
| 235 | if __name__ == "__main__": |
| 236 | main() |