├── .coveragerc ├── .gitignore ├── .nvmrc ├── .prettierrc ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── TryPython ├── TryPython │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── core │ ├── __init__.py │ ├── admin.py │ ├── ast_utils.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── eval.py │ │ │ └── rollup_server.py │ ├── static │ │ ├── css │ │ │ └── try_python.css │ │ ├── images │ │ │ ├── python.png │ │ │ └── python.svg │ │ └── js │ │ │ ├── lib │ │ │ └── jquery.console.js │ │ │ ├── src │ │ │ ├── alerts.js │ │ │ ├── index.js │ │ │ ├── python_console.js │ │ │ ├── python_console_rest_api.js │ │ │ └── utils.js │ │ │ └── test │ │ │ └── python_console_tests.js │ ├── templates │ │ └── main.html │ ├── tests │ │ ├── __init__.py │ │ ├── test_ast_utils.py │ │ ├── test_commands.py │ │ └── test_views.py │ └── views.py ├── manage.py └── tutorial │ ├── __init__.py │ ├── admin.py │ ├── fixtures │ └── steps.json │ ├── migrations │ ├── 0001_initial.py │ └── __init__.py │ ├── models.py │ ├── tests │ ├── __init__.py │ ├── test_models.py │ └── test_views.py │ └── views.py ├── docker ├── Dockerfile └── eval.py ├── package.json ├── python-content ├── en │ └── syllabus.txt └── pt-br │ └── syllabus.txt ├── requirements ├── dev_requirements.txt ├── production_requirements.txt └── requirements.txt ├── rollup.config.js ├── tox.ini ├── try-python.gif └── yarn.lock /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = 3 | TryPython/core/ 4 | TryPython/tutorial/ 5 | omit = 6 | **/tests/* 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | **/dist 4 | *.py[co] 5 | venv 6 | htmlcov 7 | .tox 8 | .coverage 9 | .env 10 | *.sqlite3 11 | yarn-error.log -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v14.6.0 -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "semi": false, 4 | "singleQuote": true, 5 | "trailingComma": "es5", 6 | "arrowParens": "avoid" 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | node_js: 3 | - "stable" 4 | 5 | before_script: 6 | - npm install -g grunt-cli bower 7 | - npm install 8 | - bower install 9 | - pip install tox 10 | 11 | script: 12 | - npm test 13 | - tox 14 | 15 | after_success: 16 | - coveralls 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to TryPython 2 | 3 | ### First of all, thanks for contributing :tada: :tada: :tada: 4 | 5 | #### Install the dev dependencies 6 | 7 | 8 | * Install python dev dependencies 9 | 10 | pip install -r requirements/dev_requirements.txt 11 | 12 | * Run js tests and lint 13 | 14 | npm test # on project root path 15 | 16 | * Run python Tests and pep8 17 | 18 | tox # on project root path 19 | 20 | #### **Did you find a bug?** 21 | 22 | * **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/IuryAlves/TryPython/issues). 23 | 24 | * If unable to find an open issue addressing the problem, [open a new one](https://github.com/IuryAlves/TryPython/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. 25 | 26 | 27 | #### **Did you write a patch that fixes a bug?** 28 | 29 | * Open a new GitHub pull request with the patch. 30 | 31 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 32 | 33 | * Make sure that the tests are passing. You can see the builds [here](https://travis-ci.org/IuryAlves/TryPython) 34 | 35 | #### **Do you intend to add a new feature or change an existing one?** 36 | 37 | * Suggest your change by creating a issue with the ```enhancement``` label. 38 | 39 | #### **Do you fix the bug, but don't know how to write tests?** 40 | 41 | * No problem. Submit a pull request and someone will help you writing the tests. 42 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | jasmine : { 4 | // Your project's source files 5 | src : [ 6 | 'TryPython/core/static/js/dist/scripts.js', 7 | ], 8 | options : { 9 | specs : [ 10 | 'TryPython/core/static/js/test/specHelper.js', 11 | 'TryPython/core/static/js/test/*.js' 12 | ], 13 | } 14 | }, 15 | jshint: { 16 | all: [ 17 | 'TryPython/core/static/js/src/*.js', 18 | 'TryPython/core/static/js/test/*.js' 19 | ] 20 | }, 21 | concat: { 22 | dist: { 23 | src: ['bower_components/jquery/dist/jquery.min.js', 24 | 'TryPython/core/static/js/lib/jquery.console.js', 25 | 'TryPython/core/static/js/src/!(main).js', // main.js must be last file 26 | 'TryPython/core/static/js/src/main.js' 27 | ], 28 | dest: 'TryPython/core/static/js/dist/scripts.js', 29 | }, 30 | }, 31 | watch: { 32 | options: {livereload: true}, 33 | javascript: { 34 | files: ['TryPython/core/static/js/src/*.js'], 35 | tasks: ['concat'] 36 | } 37 | } 38 | }); 39 | 40 | grunt.loadNpmTasks('grunt-contrib-jasmine'); 41 | grunt.loadNpmTasks('grunt-contrib-jshint'); 42 | grunt.loadNpmTasks('grunt-contrib-concat'); 43 | grunt.loadNpmTasks('grunt-contrib-watch'); 44 | 45 | // Default task. 46 | grunt.registerTask('test', ['concat', 'jasmine', 'jshint']); 47 | grunt.registerTask('build', ['concat']); 48 | grunt.registerTask('default', ['concat', 'watch']); 49 | }; 50 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Iury Alves de Souza 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Try Python [![Build Status](https://travis-ci.org/IuryAlves/TryPython.svg?branch=master)](https://travis-ci.org/IuryAlves/TryPython) [![Coverage Status](https://coveralls.io/repos/IuryAlves/TryPython/badge.svg?branch=master&service=github)](https://coveralls.io/github/IuryAlves/TryPython?branch=master) [![For the badge](http://forthebadge.com/images/badges/built-with-love.svg)](http://forthebadge.com/images/badges/built-with-love.svg) 2 | 3 | ![try-python](try-python.gif) 4 | 5 | ##### The goal of this project is to teach python to new people. This project is a python version from [try-haskell](tryhaskell.org) 6 | 7 | # Table of contents 8 | 9 | 1. [Installing](#installing) 10 | 2. [running](#running) 11 | 3. [Contributing](#contributing) 12 | 13 | ## Installing 14 | 15 | - Clone this repository 16 | 17 | ```sh 18 | git clone git@github.com:IuryAlves/TryPython.git 19 | cd TryPython 20 | ``` 21 | 22 | - Install python 2.x on your system 23 | - Install git 24 | - Install nodejs 25 | - Make sure that `yarn` is installed, open a terminal and type: 26 | 27 | ```sh 28 | yarn -v 29 | ``` 30 | 31 | - Install npm dependencies 32 | 33 | ```sh 34 | yarn 35 | ``` 36 | 37 | - Install python virtualenv: 38 | 39 | ```sh 40 | sudo pip install virtualenv 41 | ``` 42 | 43 | - Create a virtualenv: 44 | 45 | ```sh 46 | virtualenv venv 47 | source venv/bin/activate 48 | ``` 49 | 50 | - Install project backend dependencies: 51 | 52 | ```sh 53 | pip install -r requirements/requirements.txt 54 | ``` 55 | 56 | - Generate a DJANGO_SECRET_KEY [here](http://www.miniwebtool.com/django-secret-key-generator/) 57 | 58 | - Create a `.env` file in the home of the project and insert this lines 59 | 60 | DEBUG=True 61 | 62 | DJANGO_SECRET_KEY=[the-secret-key-that-you-gerenate] 63 | 64 | **Then save and close the file.** 65 | 66 | - Run the migrations: 67 | 68 | ```sh 69 | TryPython/manage.py migrate 70 | ``` 71 | 72 | - Load the fixtures of the project 73 | 74 | ```sh 75 | TryPython/manage.py loaddata steps.json 76 | ``` 77 | 78 | ## Running 79 | 80 | - Run 81 | 82 | ```sh 83 | TryPython/manage.py rollup_server 84 | ``` 85 | 86 | Now, just access [localhost:8000](localhost:8080) 87 | 88 | ## Contributing 89 | 90 | - See the contributing [guide](CONTRIBUTING.md) 91 | 92 | - This project uses TravisCI [TryPython on Travis](https://travis-ci.org/IuryAlves/TryPython) 93 | - This project uses [jquery.console](https://github.com/chrisdone/jquery-console) 94 | -------------------------------------------------------------------------------- /TryPython/TryPython/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinQuarantine/TryPython/358cb62fc0dab9da2a30e97b718888a366c4fd5c/TryPython/TryPython/__init__.py -------------------------------------------------------------------------------- /TryPython/TryPython/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from decouple import config 3 | 4 | dirname = os.path.dirname 5 | BASE_DIR = dirname(dirname(os.path.abspath(__file__))) 6 | PROJECT_DIR = dirname(BASE_DIR) 7 | 8 | SECRET_KEY = config("DJANGO_SECRET_KEY") 9 | DEBUG = config("DEBUG", default=False, cast=bool) 10 | STATIC_ROOT = os.path.join(BASE_DIR, "static/") 11 | PRODUCTION = config("PRODUCTION", default=False, cast=bool) 12 | DOCKER_BASE_URL = 'unix://var/run/docker.sock' 13 | DOCKER_IMAGE = 'iury/try-python' 14 | ALLOWED_HOSTS = ["*"] 15 | 16 | INSTALLED_APPS = ( 17 | 'django.contrib.admin', 18 | 'django.contrib.auth', 19 | 'django.contrib.contenttypes', 20 | 'django.contrib.sessions', 21 | 'django.contrib.messages', 22 | 'django.contrib.staticfiles', 23 | 'core', 24 | 'tutorial' 25 | ) 26 | 27 | MIDDLEWARE_CLASSES = ( 28 | 'django.contrib.sessions.middleware.SessionMiddleware', 29 | 'django.middleware.common.CommonMiddleware', 30 | 'django.middleware.csrf.CsrfViewMiddleware', 31 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 32 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 33 | 'django.contrib.messages.middleware.MessageMiddleware', 34 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 35 | 'django.middleware.security.SecurityMiddleware', 36 | ) 37 | 38 | ROOT_URLCONF = 'TryPython.urls' 39 | 40 | TEMPLATES = [ 41 | { 42 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 43 | 'DIRS': ['core/templates'], 44 | 'APP_DIRS': True, 45 | 'OPTIONS': { 46 | 'context_processors': [ 47 | 'django.template.context_processors.debug', 48 | 'django.template.context_processors.request', 49 | 'django.contrib.auth.context_processors.auth', 50 | 'django.contrib.messages.context_processors.messages', 51 | ], 52 | }, 53 | }, 54 | ] 55 | 56 | WSGI_APPLICATION = 'TryPython.wsgi.application' 57 | 58 | DATABASES = { 59 | 'default': { 60 | 'ENGINE': 'django.db.backends.sqlite3', 61 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 62 | } 63 | } 64 | 65 | LANGUAGE_CODE = 'en-us' 66 | 67 | TIME_ZONE = 'UTC' 68 | 69 | USE_I18N = True 70 | 71 | USE_L10N = True 72 | 73 | USE_TZ = True 74 | 75 | STATIC_URL = '/static/' 76 | -------------------------------------------------------------------------------- /TryPython/TryPython/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | from django.contrib import admin 3 | from core.views import IndexView, EvalView 4 | from tutorial.views import StepView 5 | 6 | urlpatterns = [ 7 | url(r'^admin/', include(admin.site.urls)), 8 | url(r"^$", IndexView.as_view()), 9 | url(r"^step/\d+$", IndexView.as_view()), 10 | url(r"^eval", EvalView.as_view()), 11 | url(r"^get_step", StepView.as_view()) 12 | ] 13 | -------------------------------------------------------------------------------- /TryPython/TryPython/wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.wsgi import get_wsgi_application 4 | from django.conf import settings 5 | 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "TryPython.settings") 7 | application = get_wsgi_application() 8 | 9 | if settings.PRODUCTION: 10 | import newrelic 11 | newrelic.agent.initialize( 12 | os.path.join(settings.PROJECT_DIR, 'newrelic.ini'), 'production') 13 | application = newrelic.agent.wsgi_application()(application) 14 | -------------------------------------------------------------------------------- /TryPython/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinQuarantine/TryPython/358cb62fc0dab9da2a30e97b718888a366c4fd5c/TryPython/core/__init__.py -------------------------------------------------------------------------------- /TryPython/core/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.sessions.models import Session 3 | 4 | 5 | class SessionAdmin(admin.ModelAdmin): 6 | 7 | def _session_data(self, obj): 8 | return obj.get_decoded() 9 | 10 | list_display = ['_session_data'] 11 | 12 | 13 | admin.site.register(Session, SessionAdmin) 14 | -------------------------------------------------------------------------------- /TryPython/core/ast_utils.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | 4 | def isFunction(stmt): 5 | """ 6 | Check if a python statement is a function 7 | >>> isFunction('a = 1') 8 | False 9 | >>> isFunction('def f():\\n return True\\n') 10 | True 11 | >>> isFunction('f = lambda x: x*x') 12 | True 13 | """ 14 | ast_tree = ast.parse(stmt) 15 | body = ast_tree.body[0] 16 | try: 17 | return isinstance(body.value, ast.Lambda) 18 | except AttributeError: 19 | return isinstance(body, ast.FunctionDef) 20 | -------------------------------------------------------------------------------- /TryPython/core/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinQuarantine/TryPython/358cb62fc0dab9da2a30e97b718888a366c4fd5c/TryPython/core/management/__init__.py -------------------------------------------------------------------------------- /TryPython/core/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinQuarantine/TryPython/358cb62fc0dab9da2a30e97b718888a366c4fd5c/TryPython/core/management/commands/__init__.py -------------------------------------------------------------------------------- /TryPython/core/management/commands/eval.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import print_function 3 | 4 | import sys 5 | import os 6 | import subprocess 7 | 8 | import docker 9 | from django.core.management.base import BaseCommand, CommandError 10 | from django.conf import settings 11 | 12 | 13 | class Command(BaseCommand): 14 | 15 | help = "Call a subprocess to eval a python expression" 16 | 17 | def add_arguments(self, parser): 18 | parser.add_argument("expression", type=str, default="") 19 | parser.add_argument("namespace", type=str) 20 | parser.add_argument("--with-docker", 21 | action='store_true', 22 | dest="with_docker", 23 | default=False, 24 | help="Run eval inside a docker container") 25 | 26 | parser.add_argument("--eval-script", 27 | dest="eval_script", 28 | type=str, 29 | default=os.path.join(settings.PROJECT_DIR, 30 | 'docker', 'eval.py'), 31 | help=""" 32 | Python script to eval the expression 33 | The defalt script is run from docker/eval.py 34 | """) 35 | 36 | def handle(self, *args, **kwargs): 37 | eval_script = kwargs.get("eval_script") 38 | expression = kwargs.get("expression") 39 | namespace = kwargs.get("namespace") 40 | with_docker = kwargs.get('with_docker') 41 | if with_docker: 42 | cli = docker.Client(base_url=settings.DOCKER_BASE_URL) 43 | command = [expression, namespace] 44 | try: 45 | container = cli.create_container(image=settings.DOCKER_IMAGE, 46 | command=command) 47 | container_id = container.get("Id") 48 | cli.start(container=container_id) 49 | except Exception as e: 50 | raise e 51 | 52 | out = '' 53 | while not out: # not sure if this is the best way 54 | out = cli.logs(container=container_id) 55 | cli.stop(container=container_id) 56 | if isinstance(out, bytes): 57 | out = out.decode('utf-8') 58 | print(out, file=self.stdout) 59 | else: 60 | command = ['python', eval_script, expression, namespace] 61 | try: 62 | out = subprocess.check_output(command) 63 | if isinstance(out, bytes): 64 | out = out.decode('utf-8') 65 | print(out, file=self.stdout) 66 | except subprocess.CalledProcessError as err: 67 | sys.stderr.write(str(err),) 68 | -------------------------------------------------------------------------------- /TryPython/core/management/commands/rollup_server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import atexit 4 | import signal 5 | 6 | from django.conf import settings 7 | from django.contrib.staticfiles.management.commands.runserver import Command \ 8 | as StaticfilesRunserverCommand 9 | 10 | 11 | class Command(StaticfilesRunserverCommand): 12 | 13 | def inner_run(self, *args, **options): 14 | self.start_rollup() 15 | return super(Command, self).inner_run(*args, **options) 16 | 17 | def start_rollup(self): 18 | self.stdout.write('>>> Starting rollup') 19 | self.rollup_process = subprocess.Popen( 20 | ['yarn watch'], 21 | shell=True, 22 | stdin=subprocess.PIPE, 23 | stdout=self.stdout, 24 | stderr=self.stderr, 25 | ) 26 | 27 | self.stdout.write('>>> Rollup process on pid {0}'.format(self.rollup_process.pid)) 28 | 29 | def kill_rollup_process(pid): 30 | self.stdout.write('>>> Closing rollup process') 31 | os.kill(pid, signal.SIGTERM) 32 | 33 | atexit.register(kill_rollup_process, self.rollup_process.pid) 34 | -------------------------------------------------------------------------------- /TryPython/core/static/css/try_python.css: -------------------------------------------------------------------------------- 1 | 2 | .message { 3 | width: 50%; 4 | position: absolute !important; 5 | left: 22%; 6 | top: 4%; 7 | } 8 | 9 | body { 10 | font-family: 'Merriweather'; 11 | } 12 | nav{ 13 | height: 71px; 14 | border-bottom: 1px; 15 | margin-bottom: 25px; 16 | border-bottom-style: solid; 17 | border-bottom-color: #7f8c8d; 18 | } 19 | 20 | .tutorial-content{ 21 | float: right; 22 | width: 50%; 23 | display: none; 24 | } 25 | 26 | .logo{ 27 | text-align: left; 28 | float: left; 29 | top: 12px; 30 | position: absolute; 31 | } 32 | 33 | .header-text{ 34 | margin-top: 37px; 35 | margin-left: 86px; 36 | color: #95A5A6; 37 | font-family: "ubuntu mono",monospace; 38 | } 39 | 40 | .right{ 41 | float: right; 42 | } 43 | 44 | /* github fork icon */ 45 | .ribbon-wrap { 46 | position: absolute; 47 | left: 46px; 48 | top: 49px; 49 | width: 245px; 50 | -ms-transform: rotate(-45deg); 51 | -webkit-transform: rotate(-45deg); 52 | transform: rotate(-45deg); 53 | border-color: #a00; 54 | border-left: 30px solid transparent; 55 | border-right: 30px solid transparent; 56 | border-bottom: 30px solid #a00; 57 | box-shadow: 0px 6px 5px -5px rgba(0, 0, 0, 0.5); 58 | z-index: 999; 59 | } 60 | .ribbon-wrap.right { 61 | left: inherit; 62 | right: -68px; 63 | top: 19px; 64 | -ms-transform: rotate(45deg); 65 | -webkit-transform: rotate(45deg); 66 | transform: rotate(45deg); 67 | } 68 | .ribbon-wrap .trap-ribbon { 69 | position: absolute; 70 | left: 31px; 71 | top: 2px; 72 | border-bottom: 1px dotted rgba(255, 255, 255, 0.7); 73 | width: 100%; 74 | text-align: center; 75 | } 76 | .ribbon-wrap .trap-ribbon a { 77 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 78 | font-size: 13px; 79 | font-weight: 700; 80 | color: #d8d8d8; 81 | text-decoration: none; 82 | text-shadow: 0 -1px rgba(0, 0, 0, 0.5); 83 | text-align: center; 84 | line-height: 22px; 85 | display: inline-block; 86 | border-top: 1px dotted rgba(255, 255, 255, 0.7); 87 | transition: color 500ms ease; 88 | } 89 | .ribbon-wrap .trap-ribbon a:hover { 90 | color: #fff; 91 | text-shadow: 0 1px rgba(0, 0, 0, 0.85); 92 | } 93 | 94 | /* github fork icon end*/ 95 | 96 | /* Console */ 97 | .console { 98 | background: #fff; 99 | font-family: "ubuntu mono",monospace; 100 | width: 50%; 101 | } 102 | .jquery-console-nofocus { 103 | opacity: 0.8; 104 | } 105 | .jquery-console-focus .jquery-console-cursor { 106 | background:#aaa; 107 | color:#eee 108 | font-weight:bold 109 | } 110 | .jquery-console-inner { 111 | height: 20em; 112 | overflow: auto; 113 | word-wrap: break-word; 114 | } 115 | 116 | .jquery-console-prompt, .jquery-console-message{ 117 | font-size: 17px; 118 | } 119 | .jquery-console-prompt-label { 120 | color: #16a085; 121 | font-weight: bold; 122 | font-size: 20px; 123 | } 124 | .jquery-console-error { 125 | color: #c0392b; 126 | } 127 | .jquery-console-value { 128 | color: #2e659c; 129 | } 130 | .jquery-console-stdout { 131 | color: #216f42; 132 | } 133 | .jquery-console-type { 134 | color: #777; 135 | } 136 | .jquery-console-welcome { 137 | color: #888; 138 | margin-bottom: 0.5em; 139 | } 140 | -------------------------------------------------------------------------------- /TryPython/core/static/images/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinQuarantine/TryPython/358cb62fc0dab9da2a30e97b718888a366c4fd5c/TryPython/core/static/images/python.png -------------------------------------------------------------------------------- /TryPython/core/static/images/python.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 19 | 20 | 21 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /TryPython/core/static/js/lib/jquery.console.js: -------------------------------------------------------------------------------- 1 | // JQuery Console 1.0 2 | // Sun Feb 21 20:28:47 GMT 2010 3 | // 4 | // Copyright 2010 Chris Done, Simon David Pratt. All rights reserved. 5 | // 6 | // Redistribution and use in source and binary forms, with or without 7 | // modification, are permitted provided that the following conditions 8 | // are met: 9 | // 10 | // 1. Redistributions of source code must retain the above 11 | // copyright notice, this list of conditions and the following 12 | // disclaimer. 13 | // 14 | // 2. Redistributions in binary form must reproduce the above 15 | // copyright notice, this list of conditions and the following 16 | // disclaimer in the documentation and/or other materials 17 | // provided with the distribution. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 | // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 | // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 25 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 29 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | // POSSIBILITY OF SUCH DAMAGE. 31 | 32 | // TESTED ON 33 | // Internet Explorer 6 34 | // Opera 10.01 35 | // Chromium 4.0.237.0 (Ubuntu build 31094) 36 | // Firefox 3.5.8, 3.6.2 (Mac) 37 | // Safari 4.0.5 (6531.22.7) (Mac) 38 | // Google Chrome 5.0.375.55 (Mac) 39 | 40 | import jquery from 'jquery' 41 | 42 | ;(function ($) { 43 | var isWebkit = !!~navigator.userAgent.indexOf(' AppleWebKit/') 44 | 45 | $.fn.console = function (config) { 46 | //////////////////////////////////////////////////////////////////////// 47 | // Constants 48 | // Some are enums, data types, others just for optimisation 49 | var keyCodes = { 50 | // left 51 | 37: moveBackward, 52 | // right 53 | 39: moveForward, 54 | // up 55 | 38: previousHistory, 56 | // down 57 | 40: nextHistory, 58 | // backspace 59 | 8: backDelete, 60 | // delete 61 | 46: forwardDelete, 62 | // end 63 | 35: moveToEnd, 64 | // start 65 | 36: moveToStart, 66 | // return 67 | 13: commandTrigger, 68 | // tab 69 | 18: doNothing, 70 | // tab 71 | 9: doComplete, 72 | } 73 | 74 | if (config.keyCodes) { 75 | $.extend(keyCodes, config.keyCodes) 76 | } 77 | 78 | var ctrlCodes = { 79 | // C-a 80 | 65: moveToStart, 81 | // C-e 82 | 69: moveToEnd, 83 | // C-d 84 | 68: forwardDelete, 85 | // C-n 86 | 78: nextHistory, 87 | // C-p 88 | 80: previousHistory, 89 | // C-b 90 | 66: moveBackward, 91 | // C-f 92 | 70: moveForward, 93 | // C-k 94 | 75: deleteUntilEnd, 95 | // C-l 96 | 76: clearScreen, 97 | // C-u 98 | 85: clearCurrentPrompt, 99 | } 100 | if (config.ctrlCodes) { 101 | $.extend(ctrlCodes, config.ctrlCodes) 102 | } 103 | var altCodes = { 104 | // M-f 105 | 70: moveToNextWord, 106 | // M-b 107 | 66: moveToPreviousWord, 108 | // M-d 109 | 68: deleteNextWord, 110 | } 111 | var shiftCodes = { 112 | // return 113 | 13: newLine, 114 | } 115 | var cursor = ' ' 116 | 117 | //////////////////////////////////////////////////////////////////////// 118 | // Globals 119 | var container = $(this) 120 | var inner = $('
') 121 | // erjiang: changed this from a text input to a textarea so we 122 | // can get pasted newlines 123 | var typer = $( 124 | '' 125 | ) 126 | // Prompt 127 | var promptBox 128 | var prompt 129 | var continuedPromptLabel = 130 | config && config.continuedPromptLabel ? config.continuedPromptLabel : '> ' 131 | var column = 0 132 | var promptText = '' 133 | var restoreText = '' 134 | var continuedText = '' 135 | var fadeOnReset = config.fadeOnReset !== undefined ? config.fadeOnReset : true 136 | // Prompt history stack 137 | var history = [] 138 | var ringn = 0 139 | // For reasons unknown to The Sword of Michael himself, Opera 140 | // triggers and sends a key character when you hit various 141 | // keys like PgUp, End, etc. So there is no way of knowing 142 | // when a user has typed '#' or End. My solution is in the 143 | // typer.keydown and typer.keypress functions; I use the 144 | // variable below to ignore the keypress event if the keydown 145 | // event succeeds. 146 | var cancelKeyPress = 0 147 | // When this value is false, the prompt will not respond to input 148 | var acceptInput = true 149 | // When this value is true, the command has been canceled 150 | var cancelCommand = false 151 | 152 | // External exports object 153 | var extern = {} 154 | 155 | //////////////////////////////////////////////////////////////////////// 156 | // Main entry point 157 | ;(function () { 158 | extern.promptLabel = config && config.promptLabel ? config.promptLabel : '> ' 159 | container.append(inner) 160 | inner.append(typer) 161 | typer.css({ position: 'absolute', top: 0, left: '-9999px' }) 162 | if (config.welcomeMessage) message(config.welcomeMessage, 'jquery-console-welcome') 163 | newPromptBox() 164 | if (config.autofocus) { 165 | inner.addClass('jquery-console-focus') 166 | typer.focus() 167 | setTimeout(function () { 168 | inner.addClass('jquery-console-focus') 169 | typer.focus() 170 | }, 100) 171 | } 172 | extern.inner = inner 173 | extern.typer = typer 174 | extern.scrollToBottom = scrollToBottom 175 | extern.report = report 176 | extern.showCompletion = showCompletion 177 | extern.clearScreen = clearScreen 178 | })() 179 | 180 | //////////////////////////////////////////////////////////////////////// 181 | // Reset terminal 182 | extern.reset = function () { 183 | var welcome = typeof config.welcomeMessage != 'undefined' 184 | 185 | var removeElements = function () { 186 | inner.find('div').each(function () { 187 | if (!welcome) { 188 | $(this).remove() 189 | } else { 190 | welcome = false 191 | } 192 | }) 193 | } 194 | 195 | if (fadeOnReset) { 196 | inner.parent().fadeOut(function () { 197 | removeElements() 198 | newPromptBox() 199 | inner.parent().fadeIn(focusConsole) 200 | }) 201 | } else { 202 | removeElements() 203 | newPromptBox() 204 | focusConsole() 205 | } 206 | } 207 | 208 | var focusConsole = function () { 209 | inner.addClass('jquery-console-focus') 210 | typer.focus() 211 | } 212 | 213 | extern.focus = function () { 214 | focusConsole() 215 | } 216 | 217 | //////////////////////////////////////////////////////////////////////// 218 | // Reset terminal 219 | extern.notice = function (msg, style) { 220 | var n = $('
') 221 | .append($('
').text(msg)) 222 | .css({ visibility: 'hidden' }) 223 | container.append(n) 224 | var focused = true 225 | if (style == 'fadeout') 226 | setTimeout(function () { 227 | n.fadeOut(function () { 228 | n.remove() 229 | }) 230 | }, 4000) 231 | else if (style == 'prompt') { 232 | var a = $( 233 | '
OK
' 234 | ) 235 | n.append(a) 236 | focused = false 237 | a.click(function () { 238 | n.fadeOut(function () { 239 | n.remove() 240 | inner.css({ opacity: 1 }) 241 | }) 242 | }) 243 | } 244 | var h = n.height() 245 | n.css({ height: '0px', visibility: 'visible' }).animate({ height: h + 'px' }, function () { 246 | if (!focused) inner.css({ opacity: 0.5 }) 247 | }) 248 | n.css('cursor', 'default') 249 | return n 250 | } 251 | 252 | //////////////////////////////////////////////////////////////////////// 253 | // Make a new prompt box 254 | function newPromptBox() { 255 | column = 0 256 | promptText = '' 257 | ringn = 0 // Reset the position of the history ring 258 | enableInput() 259 | promptBox = $('
') 260 | var label = $('') 261 | var labelText = extern.continuedPrompt ? continuedPromptLabel : extern.promptLabel 262 | promptBox.append(label.text(labelText).show()) 263 | label.html(label.html().replace(' ', ' ')) 264 | prompt = $('') 265 | promptBox.append(prompt) 266 | inner.append(promptBox) 267 | updatePromptDisplay() 268 | } 269 | 270 | //////////////////////////////////////////////////////////////////////// 271 | // Handle setting focus 272 | container.click(function () { 273 | // Don't mess with the focus if there is an active selection 274 | if (window.getSelection().toString()) { 275 | return false 276 | } 277 | 278 | inner.addClass('jquery-console-focus') 279 | inner.removeClass('jquery-console-nofocus') 280 | if (isWebkit) { 281 | typer.focusWithoutScrolling() 282 | } else { 283 | typer.css('position', 'fixed').focus() 284 | } 285 | scrollToBottom() 286 | return false 287 | }) 288 | 289 | //////////////////////////////////////////////////////////////////////// 290 | // Handle losing focus 291 | typer.blur(function () { 292 | inner.removeClass('jquery-console-focus') 293 | inner.addClass('jquery-console-nofocus') 294 | }) 295 | 296 | //////////////////////////////////////////////////////////////////////// 297 | // Bind to the paste event of the input box so we know when we 298 | // get pasted data 299 | typer.bind('paste', function (e) { 300 | // wipe typer input clean just in case 301 | typer.val('') 302 | // this timeout is required because the onpaste event is 303 | // fired *before* the text is actually pasted 304 | setTimeout(function () { 305 | typer.consoleInsert(typer.val()) 306 | typer.val('') 307 | }, 0) 308 | }) 309 | 310 | //////////////////////////////////////////////////////////////////////// 311 | // Handle key hit before translation 312 | // For picking up control characters like up/left/down/right 313 | 314 | typer.keydown(function (e) { 315 | cancelKeyPress = 0 316 | var keyCode = e.keyCode 317 | // C-c: cancel the execution 318 | if (e.ctrlKey && keyCode == 67) { 319 | cancelKeyPress = keyCode 320 | cancelExecution() 321 | return false 322 | } 323 | if (acceptInput) { 324 | if (e.shiftKey && keyCode in shiftCodes) { 325 | cancelKeyPress = keyCode 326 | shiftCodes[keyCode]() 327 | return false 328 | } else if (e.altKey && keyCode in altCodes) { 329 | cancelKeyPress = keyCode 330 | altCodes[keyCode]() 331 | return false 332 | } else if (e.ctrlKey && keyCode in ctrlCodes) { 333 | cancelKeyPress = keyCode 334 | ctrlCodes[keyCode]() 335 | return false 336 | } else if (keyCode in keyCodes) { 337 | cancelKeyPress = keyCode 338 | keyCodes[keyCode]() 339 | return false 340 | } 341 | } 342 | }) 343 | 344 | //////////////////////////////////////////////////////////////////////// 345 | // Handle key press 346 | typer.keypress(function (e) { 347 | var keyCode = e.keyCode || e.which 348 | if (isIgnorableKey(e)) { 349 | return false 350 | } 351 | // C-v: don't insert on paste event 352 | if ((e.ctrlKey || e.metaKey) && String.fromCharCode(keyCode).toLowerCase() == 'v') { 353 | return true 354 | } 355 | if (acceptInput && cancelKeyPress != keyCode && keyCode >= 32) { 356 | if (cancelKeyPress) return false 357 | if ( 358 | typeof config.charInsertTrigger == 'undefined' || 359 | (typeof config.charInsertTrigger == 'function' && 360 | config.charInsertTrigger(keyCode, promptText)) 361 | ) { 362 | typer.consoleInsert(keyCode) 363 | } 364 | } 365 | if (isWebkit) return false 366 | }) 367 | 368 | function isIgnorableKey(e) { 369 | // for now just filter alt+tab that we receive on some platforms when 370 | // user switches windows (goes away from the browser) 371 | return (e.keyCode == keyCodes.tab || e.keyCode == 192) && e.altKey 372 | } 373 | 374 | //////////////////////////////////////////////////////////////////////// 375 | // Rotate through the command history 376 | function rotateHistory(n) { 377 | if (history.length == 0) return 378 | ringn += n 379 | if (ringn < 0) ringn = history.length 380 | else if (ringn > history.length) ringn = 0 381 | var prevText = promptText 382 | if (ringn == 0) { 383 | promptText = restoreText 384 | } else { 385 | promptText = history[ringn - 1] 386 | } 387 | if (config.historyPreserveColumn) { 388 | if (promptText.length < column + 1) { 389 | column = promptText.length 390 | } else if (column == 0) { 391 | column = promptText.length 392 | } 393 | } else { 394 | column = promptText.length 395 | } 396 | updatePromptDisplay() 397 | } 398 | 399 | function previousHistory() { 400 | rotateHistory(-1) 401 | } 402 | 403 | function nextHistory() { 404 | rotateHistory(1) 405 | } 406 | 407 | // Add something to the history ring 408 | function addToHistory(line) { 409 | history.push(line) 410 | restoreText = '' 411 | } 412 | 413 | // Delete the character at the current position 414 | function deleteCharAtPos() { 415 | if (column < promptText.length) { 416 | promptText = promptText.substring(0, column) + promptText.substring(column + 1) 417 | restoreText = promptText 418 | return true 419 | } else return false 420 | } 421 | 422 | function backDelete() { 423 | if (moveColumn(-1)) { 424 | deleteCharAtPos() 425 | updatePromptDisplay() 426 | } 427 | } 428 | 429 | function forwardDelete() { 430 | if (deleteCharAtPos()) { 431 | updatePromptDisplay() 432 | } 433 | } 434 | 435 | function deleteUntilEnd() { 436 | while (deleteCharAtPos()) { 437 | updatePromptDisplay() 438 | } 439 | } 440 | 441 | function clearCurrentPrompt() { 442 | extern.promptText('') 443 | } 444 | 445 | function clearScreen() { 446 | inner.children('.jquery-console-prompt-box, .jquery-console-message').remove() 447 | extern.report(' ') 448 | extern.promptText('') 449 | extern.focus() 450 | } 451 | 452 | function deleteNextWord() { 453 | // A word is defined within this context as a series of alphanumeric 454 | // characters. 455 | // Delete up to the next alphanumeric character 456 | while (column < promptText.length && !isCharAlphanumeric(promptText[column])) { 457 | deleteCharAtPos() 458 | updatePromptDisplay() 459 | } 460 | // Then, delete until the next non-alphanumeric character 461 | while (column < promptText.length && isCharAlphanumeric(promptText[column])) { 462 | deleteCharAtPos() 463 | updatePromptDisplay() 464 | } 465 | } 466 | 467 | function newLine() { 468 | var lines = promptText.split('\n') 469 | var last_line = lines.slice(-1)[0] 470 | var spaces = last_line.match(/^(\s*)/g)[0] 471 | var new_line = '\n' + spaces 472 | promptText += new_line 473 | moveColumn(new_line.length) 474 | updatePromptDisplay() 475 | } 476 | 477 | //////////////////////////////////////////////////////////////////////// 478 | // Validate command and trigger it if valid, or show a validation error 479 | function commandTrigger() { 480 | var line = promptText 481 | if (typeof config.commandValidate == 'function') { 482 | var ret = config.commandValidate(line) 483 | if (ret == true || ret == false) { 484 | if (ret) { 485 | handleCommand() 486 | } 487 | } else { 488 | commandResult(ret, 'jquery-console-message-error') 489 | } 490 | } else { 491 | handleCommand() 492 | } 493 | } 494 | 495 | // Scroll to the bottom of the view 496 | function scrollToBottom() { 497 | var version = jQuery.fn.jquery.split('.') 498 | var major = parseInt(version[0]) 499 | var minor = parseInt(version[1]) 500 | 501 | // check if we're using jquery > 1.6 502 | if ((major == 1 && minor > 6) || major > 1) { 503 | inner.prop({ scrollTop: inner.prop('scrollHeight') }) 504 | } else { 505 | inner.attr({ scrollTop: inner.attr('scrollHeight') }) 506 | } 507 | } 508 | 509 | function cancelExecution() { 510 | if (typeof config.cancelHandle == 'function') { 511 | config.cancelHandle() 512 | } 513 | } 514 | 515 | //////////////////////////////////////////////////////////////////////// 516 | // Handle a command 517 | function handleCommand() { 518 | if (typeof config.commandHandle == 'function') { 519 | disableInput() 520 | addToHistory(promptText) 521 | var text = promptText 522 | if (extern.continuedPrompt) { 523 | if (continuedText) continuedText += '\n' + promptText 524 | else continuedText = promptText 525 | } else continuedText = undefined 526 | if (continuedText) text = continuedText 527 | var ret = config.commandHandle(text, function (msgs) { 528 | commandResult(msgs) 529 | }) 530 | if (extern.continuedPrompt && !continuedText) continuedText = promptText 531 | if (typeof ret == 'boolean') { 532 | if (ret) { 533 | // Command succeeded without a result. 534 | commandResult() 535 | } else { 536 | commandResult('Command failed.', 'jquery-console-message-error') 537 | } 538 | } else if (typeof ret == 'string') { 539 | commandResult(ret, 'jquery-console-message-success') 540 | } else if (typeof ret == 'object' && ret.length) { 541 | commandResult(ret) 542 | } else if (extern.continuedPrompt) { 543 | commandResult() 544 | } 545 | } 546 | } 547 | 548 | //////////////////////////////////////////////////////////////////////// 549 | // Disable input 550 | function disableInput() { 551 | acceptInput = false 552 | } 553 | 554 | // Enable input 555 | function enableInput() { 556 | acceptInput = true 557 | } 558 | 559 | //////////////////////////////////////////////////////////////////////// 560 | // Reset the prompt in invalid command 561 | function commandResult(msg, className) { 562 | column = -1 563 | updatePromptDisplay() 564 | if (typeof msg == 'string') { 565 | message(msg, className) 566 | } else if ($.isArray(msg)) { 567 | for (var x in msg) { 568 | var ret = msg[x] 569 | message(ret.msg, ret.className) 570 | } 571 | } else { 572 | // Assume it's a DOM node or jQuery object. 573 | inner.append(msg) 574 | } 575 | newPromptBox() 576 | } 577 | 578 | //////////////////////////////////////////////////////////////////////// 579 | // Report some message into the console 580 | function report(msg, className) { 581 | var text = promptText 582 | promptBox.remove() 583 | commandResult(msg, className) 584 | extern.promptText(text) 585 | } 586 | 587 | //////////////////////////////////////////////////////////////////////// 588 | // Display a message 589 | function message(msg, className) { 590 | var mesg = $('
') 591 | if (className) mesg.addClass(className) 592 | mesg.filledText(msg).hide() 593 | inner.append(mesg) 594 | mesg.show() 595 | } 596 | 597 | //////////////////////////////////////////////////////////////////////// 598 | // Handle normal character insertion 599 | // data can either be a number, which will be interpreted as the 600 | // numeric value of a single character, or a string 601 | typer.consoleInsert = function (data) { 602 | // TODO: remove redundant indirection 603 | var text = typeof data == 'number' ? String.fromCharCode(data) : data 604 | var before = promptText.substring(0, column) 605 | var after = promptText.substring(column) 606 | promptText = before + text + after 607 | moveColumn(text.length) 608 | restoreText = promptText 609 | updatePromptDisplay() 610 | } 611 | 612 | //////////////////////////////////////////////////////////////////////// 613 | // Move to another column relative to this one 614 | // Negative means go back, positive means go forward. 615 | function moveColumn(n) { 616 | if (column + n >= 0 && column + n <= promptText.length) { 617 | column += n 618 | return true 619 | } else return false 620 | } 621 | 622 | function moveForward() { 623 | if (moveColumn(1)) { 624 | updatePromptDisplay() 625 | return true 626 | } 627 | return false 628 | } 629 | 630 | function moveBackward() { 631 | if (moveColumn(-1)) { 632 | updatePromptDisplay() 633 | return true 634 | } 635 | return false 636 | } 637 | 638 | function moveToStart() { 639 | if (moveColumn(-column)) updatePromptDisplay() 640 | } 641 | 642 | function moveToEnd() { 643 | if (moveColumn(promptText.length - column)) updatePromptDisplay() 644 | } 645 | 646 | function moveToNextWord() { 647 | while ( 648 | column < promptText.length && 649 | !isCharAlphanumeric(promptText[column]) && 650 | moveForward() 651 | ) {} 652 | while ( 653 | column < promptText.length && 654 | isCharAlphanumeric(promptText[column]) && 655 | moveForward() 656 | ) {} 657 | } 658 | 659 | function moveToPreviousWord() { 660 | // Move backward until we find the first alphanumeric 661 | while (column - 1 >= 0 && !isCharAlphanumeric(promptText[column - 1]) && moveBackward()) {} 662 | // Move until we find the first non-alphanumeric 663 | while (column - 1 >= 0 && isCharAlphanumeric(promptText[column - 1]) && moveBackward()) {} 664 | } 665 | 666 | function isCharAlphanumeric(charToTest) { 667 | if (typeof charToTest == 'string') { 668 | var code = charToTest.charCodeAt() 669 | return ( 670 | (code >= 'A'.charCodeAt() && code <= 'Z'.charCodeAt()) || 671 | (code >= 'a'.charCodeAt() && code <= 'z'.charCodeAt()) || 672 | (code >= '0'.charCodeAt() && code <= '9'.charCodeAt()) 673 | ) 674 | } 675 | return false 676 | } 677 | 678 | function doComplete() { 679 | if (typeof config.completeHandle == 'function') { 680 | doCompleteDirectly() 681 | } else { 682 | issueComplete() 683 | } 684 | } 685 | 686 | function doCompleteDirectly() { 687 | if (typeof config.completeHandle == 'function') { 688 | var completions = config.completeHandle(promptText) 689 | var len = completions.length 690 | if (len === 1) { 691 | extern.promptText(promptText + completions[0]) 692 | } else if (len > 1 && config.cols) { 693 | var prompt = promptText 694 | // Compute the number of rows that will fit in the width 695 | var max = 0 696 | for (var i = 0; i < len; i++) { 697 | max = Math.max(max, completions[i].length) 698 | } 699 | max += 2 700 | var n = Math.floor(config.cols / max) 701 | var buffer = '' 702 | var col = 0 703 | for (i = 0; i < len; i++) { 704 | var completion = completions[i] 705 | buffer += completions[i] 706 | for (var j = completion.length; j < max; j++) { 707 | buffer += ' ' 708 | } 709 | if (++col >= n) { 710 | buffer += '\n' 711 | col = 0 712 | } 713 | } 714 | commandResult(buffer, 'jquery-console-message-value') 715 | extern.promptText(prompt) 716 | } 717 | } 718 | } 719 | 720 | function issueComplete() { 721 | if (typeof config.completeIssuer == 'function') { 722 | config.completeIssuer(promptText) 723 | } 724 | } 725 | 726 | function showCompletion(promptText, completions) { 727 | var len = completions.length 728 | if (len === 1) { 729 | extern.promptText(promptText + completions[0]) 730 | } else if (len > 1 && config.cols) { 731 | var prompt = promptText 732 | // Compute the number of rows that will fit in the width 733 | var max = 0 734 | for (var i = 0; i < len; i++) { 735 | max = Math.max(max, completions[i].length) 736 | } 737 | max += 2 738 | var n = Math.floor(config.cols / max) 739 | var buffer = '' 740 | var col = 0 741 | for (i = 0; i < len; i++) { 742 | var completion = completions[i] 743 | buffer += completions[i] 744 | for (var j = completion.length; j < max; j++) { 745 | buffer += ' ' 746 | } 747 | if (++col >= n) { 748 | buffer += '\n' 749 | col = 0 750 | } 751 | } 752 | commandResult(buffer, 'jquery-console-message-value') 753 | extern.promptText(prompt) 754 | } 755 | } 756 | 757 | function doNothing() {} 758 | 759 | extern.promptText = function (text) { 760 | if (typeof text === 'string') { 761 | promptText = text 762 | column = promptText.length 763 | updatePromptDisplay() 764 | } 765 | return promptText 766 | } 767 | 768 | //////////////////////////////////////////////////////////////////////// 769 | // Update the prompt display 770 | function updatePromptDisplay() { 771 | var line = promptText 772 | var html = '' 773 | if (column > 0 && line == '') { 774 | // When we have an empty line just display a cursor. 775 | html = cursor 776 | } else if (column == promptText.length) { 777 | // We're at the end of the line, so we need to display 778 | // the text *and* cursor. 779 | html = htmlEncode(line) + cursor 780 | } else { 781 | // Grab the current character, if there is one, and 782 | // make it the current cursor. 783 | var before = line.substring(0, column) 784 | var current = line.substring(column, column + 1) 785 | if (current) { 786 | current = '' + htmlEncode(current) + '' 787 | } 788 | var after = line.substring(column + 1) 789 | html = htmlEncode(before) + current + htmlEncode(after) 790 | } 791 | prompt.html(html) 792 | scrollToBottom() 793 | } 794 | 795 | // Simple HTML encoding 796 | // Simply replace '<', '>' and '&' 797 | // TODO: Use jQuery's .html() trick, or grab a proper, fast 798 | // HTML encoder. 799 | function htmlEncode(text) { 800 | return text 801 | .replace(/&/g, '&') 802 | .replace(/') 806 | } 807 | 808 | return extern 809 | } 810 | // Simple utility for printing messages 811 | $.fn.filledText = function (txt) { 812 | $(this).text(txt) 813 | $(this).html($(this).html().replace(/\t/g, '  ').replace(/\n/g, '
')) 814 | return this 815 | } 816 | 817 | // Alternative method for focus without scrolling 818 | $.fn.focusWithoutScrolling = function () { 819 | var x = window.scrollX, 820 | y = window.scrollY 821 | $(this).focus() 822 | window.scrollTo(x, y) 823 | } 824 | })(jquery) 825 | -------------------------------------------------------------------------------- /TryPython/core/static/js/src/alerts.js: -------------------------------------------------------------------------------- 1 | export default { 2 | show_error_alert: function (message, sub_message) { 3 | var template = 4 | '
' + 5 | '' + 6 | '
' + 7 | message + 8 | '

' + 9 | sub_message + 10 | '

' 11 | $('body').prepend(template).fadeIn('slow') 12 | }, 13 | close_alert: function () { 14 | var alert = $('.message') 15 | alert.fadeOut('slow', function () { 16 | $(this).remove() 17 | }) 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /TryPython/core/static/js/src/index.js: -------------------------------------------------------------------------------- 1 | import jquery from 'jquery' 2 | import '../lib/jquery.console' 3 | window.$ = jquery 4 | window.jquery = jquery 5 | window.jQuery = jquery 6 | import pyConsole from './python_console' 7 | import pythonConsoleRestApi from './python_console_rest_api' 8 | 9 | pythonConsoleRestApi.init() 10 | pyConsole.init() 11 | -------------------------------------------------------------------------------- /TryPython/core/static/js/src/python_console.js: -------------------------------------------------------------------------------- 1 | import alerts from './alerts' 2 | import utils from './utils' 3 | import pythonConsoleRestApi from './python_console_rest_api' 4 | 5 | var _get_last_statement = function (line) { 6 | var splited_line = line.split('\n') 7 | return splited_line[splited_line.length - 1] 8 | } 9 | 10 | var insert_space = function (quantity) { 11 | var i = 0 12 | if (!quantity) { 13 | quantity = 4 14 | } 15 | while (i < quantity) { 16 | api.console.typer.consoleInsert(32) //inserts space 17 | i += 1 18 | } 19 | } 20 | 21 | //public 22 | const api = { 23 | statements: '', 24 | console_options: { 25 | commandValidate: function (line) { 26 | return true 27 | }, 28 | keyCodes: { 29 | 9: insert_space, 30 | 18: insert_space, 31 | }, 32 | promptLabel: '>>> ', 33 | continuedPromptLabel: '...', 34 | autofocus: true, 35 | fadeOnReset: false, 36 | commandHandle: function (line, report) { 37 | if (!line) { 38 | return '' 39 | } 40 | if (line.trim() === 'next') { 41 | api.get_step(window.location.href) 42 | api.console.reset() 43 | return 44 | } 45 | if (api.console.continuedPrompt) { 46 | var last_statement = _get_last_statement(line) 47 | if (last_statement.trim()) { 48 | api.statements += '\n' + last_statement 49 | return 50 | } 51 | } 52 | if (line.endsWith(':')) { 53 | api.console.continuedPrompt = true 54 | api.statements += _get_last_statement(line) 55 | return 56 | } else { 57 | api.console.continuedPrompt = false 58 | } 59 | if (api.statements) { 60 | line = api.statements + '\n' 61 | api.statements = '' 62 | } 63 | api.rest_api.sendPythonExpression( 64 | line, 65 | function (result) { 66 | var msgs = [] 67 | var out = result.out 68 | var err = result.err 69 | 70 | if (out.trim()) { 71 | out = out.split('\n') 72 | for (var l in out) { 73 | const line_out = out[l] 74 | if (line_out.trim()) { 75 | msgs.push({ 76 | msg: ' ' + line_out, 77 | className: 'jquery-console-message-value', 78 | }) 79 | } 80 | } 81 | report(msgs) 82 | } else { 83 | report([ 84 | { 85 | msg: err.trim(), 86 | className: 'jquery-console-error', 87 | }, 88 | ]) 89 | return 90 | } 91 | }, 92 | function () { 93 | alerts.show_error_alert( 94 | 'Ops! Algo deu errado.', 95 | 'Se o problema persistir entre em contato conosco por' + 96 | " aqui." 98 | ) 99 | report() 100 | } 101 | ) 102 | }, 103 | }, 104 | get_step: function (url) { 105 | api.current_step = utils.parse_step_from_url(url) 106 | if (api.current_step) { 107 | var tutorial_content_element = api.jquery('.tutorial-content') 108 | tutorial_content_element.empty().fadeOut('slow') 109 | api.rest_api.getStep(api.current_step, function (result) { 110 | tutorial_content_element.append('

' + result.title + '

').fadeIn('slow') 111 | tutorial_content_element.append('

' + result.content + '

').fadeIn('slow') 112 | }) 113 | } 114 | }, 115 | init: function () { 116 | api.jquery = window.$ 117 | api.rest_api = pythonConsoleRestApi 118 | 119 | api.current_step = 0 120 | api.jquery(document).ready(function () { 121 | var console_element = api.jquery('
') 122 | api.jquery('body').append(console_element) 123 | 124 | api.console = console_element.console(api.console_options) 125 | api.get_step(window.location.href) 126 | }) 127 | }, 128 | } 129 | 130 | export default api 131 | -------------------------------------------------------------------------------- /TryPython/core/static/js/src/python_console_rest_api.js: -------------------------------------------------------------------------------- 1 | // private 2 | var _getCsrfToken = function (name) { 3 | var csrf_element = document.getElementsByName(name)[0] 4 | return csrf_element.value 5 | } 6 | 7 | var _create_ajax = function (params, url, success, error) { 8 | if (!params) { 9 | params = {} 10 | } 11 | params.csrfmiddlewaretoken = _getCsrfToken('csrfmiddlewaretoken') 12 | api.jquery.ajax({ 13 | url: url, 14 | dataType: 'json', 15 | type: 'POST', 16 | data: params, 17 | success: success, 18 | error: error, 19 | }) 20 | } 21 | 22 | // public 23 | const api = { 24 | init: function () { 25 | const csrftoken = _getCsrfToken('csrfmiddlewaretoken') 26 | api.jquery = window.$ 27 | console.log(window.$) 28 | api.jquery.ajaxSetup({ 29 | beforeSend: function (xhr, settings) { 30 | if (this.crossDomain) { 31 | xhr.setRequestHeader('X-CSRFToken', csrftoken) 32 | } 33 | }, 34 | }) 35 | }, 36 | sendPythonExpression: function (expression, success, error) { 37 | _create_ajax({ toEval: expression }, '/eval', success, error) 38 | }, 39 | getStep: function (step, callback) { 40 | _create_ajax({ step: step }, '/get_step', callback) 41 | }, 42 | } 43 | 44 | export default api 45 | -------------------------------------------------------------------------------- /TryPython/core/static/js/src/utils.js: -------------------------------------------------------------------------------- 1 | if (!String.hasOwnProperty('endsWith')) { 2 | String.prototype.endsWith = function (str) { 3 | return this.match(str + '$') == str 4 | } 5 | } 6 | 7 | export default { 8 | parse_step_from_url: function (url) { 9 | var step_re = /step\/(\d+)/ 10 | var match = step_re.exec(url) 11 | if (match && match.length >= 1) { 12 | return match[1] 13 | } 14 | return null 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /TryPython/core/static/js/test/python_console_tests.js: -------------------------------------------------------------------------------- 1 | describe("Python Console Tests", function() { 2 | var python_console_rest_api, jquery, window; 3 | beforeEach(function() { 4 | python_console_rest_api = { 5 | _getCsrfToken: function(value) { 6 | return "csrftoken"; 7 | }, 8 | init: function(jquery) {}, 9 | sendPythonExpression: function(){}, 10 | getStep: function(){} 11 | }; 12 | jquery = { 13 | ready: function() {} 14 | }; 15 | window = { 16 | location: { 17 | href: '' 18 | } 19 | }; 20 | report = function(){}; 21 | 22 | spyOn(python_console_rest_api, '_getCsrfToken'); 23 | spyOn(python_console_rest_api, 'init'); 24 | spyOn(python_console_rest_api, 'sendPythonExpression'); 25 | spyOn(python_console_rest_api, 'getStep'); 26 | spyOn(jquery, 'ready'); 27 | spyOn(py_console, 'get_step').and.callThrough(); 28 | spyOn(py_console, 'init').and.callThrough(); 29 | }); 30 | 31 | it("Should init the python console", function() { 32 | py_console.init($, python_console_rest_api, window, utils); 33 | spyOn(py_console.console, 'reset').and.callThrough(); 34 | 35 | expect(py_console.jquery).toEqual($); 36 | expect(py_console.init).toHaveBeenCalledWith($, python_console_rest_api, window, utils); 37 | expect(py_console.rest_api).toEqual(python_console_rest_api); 38 | expect(python_console_rest_api.init.calls.count(), 1); 39 | expect(jquery.ready.calls.count(), 1); 40 | expect(py_console.console).not.toBe(null); 41 | expect(py_console.current_step).toBe(null); 42 | }); 43 | 44 | it("Should get the first step on document.ready when /step/number in url", function(){ 45 | var url = '/step/1'; 46 | window.location.href = url; 47 | py_console.init($, python_console_rest_api, window, utils); 48 | spyOn(py_console.console, 'reset').and.callThrough(); 49 | expect(py_console.get_step).toHaveBeenCalledWith(url); 50 | expect(python_console_rest_api.getStep).toHaveBeenCalledWith('1', jasmine.any(Function)); 51 | expect(py_console.current_step).toEqual('1'); 52 | }); 53 | 54 | it("py_console.console.promptLabel should be >>>", function() { 55 | expect(py_console.console.promptLabel).toEqual(">>> "); 56 | }); 57 | 58 | it("py_console.console_options.continuedPromptLabel should be ...", function() { 59 | expect(py_console.console_options.continuedPromptLabel).toEqual("..."); 60 | }); 61 | 62 | it("py_console.console_options.autofocus should be true", function() { 63 | expect(py_console.console_options.autofocus).toBe(true); 64 | }); 65 | 66 | it("py_console.console_options.welcomeMessage should be undefined", function() { 67 | expect(py_console.console_options.welcomeMessage).toBe(undefined); 68 | }); 69 | 70 | it("py_console.console.commandValidate with empty string should return true", function() { 71 | expect(py_console.console_options.commandValidate('')).toBe(true); 72 | }); 73 | 74 | it("py_console.console.commandHandle with expression should send the expression to backend", function() { 75 | py_console.init($, python_console_rest_api, window, utils); 76 | spyOn(py_console.console, 'reset').and.callThrough(); 77 | py_console.console_options.commandHandle('1+1', report); 78 | 79 | expect(python_console_rest_api.sendPythonExpression).toHaveBeenCalledWith('1+1', 80 | jasmine.any(Function), jasmine.any(Function)); 81 | }); 82 | 83 | it("py_console.console.commandHandle should insert \\n after :", function() { 84 | py_console.init($, python_console_rest_api, window, utils); 85 | spyOn(py_console.console, 'reset').and.callThrough(); 86 | py_console.console_options.commandHandle('for x in (1, 2, 3):', report); 87 | 88 | expect(py_console.statements).toEqual('for x in (1, 2, 3):'); 89 | expect(py_console.console.continuedPrompt).toBe(true); 90 | 91 | py_console.console_options.commandHandle(' print x', report); 92 | 93 | expect(py_console.statements).toEqual('for x in (1, 2, 3):\n print x'); 94 | expect(py_console.console.continuedPrompt).toBe(true); 95 | 96 | py_console.console_options.commandHandle('\n', report); 97 | 98 | expect(py_console.statements).toEqual(''); 99 | expect(py_console.console.continuedPrompt).toBe(false); 100 | expect(python_console_rest_api.sendPythonExpression).toHaveBeenCalledWith('for x in (1, 2, 3):\n print x\n', 101 | jasmine.any(Function), jasmine.any(Function)); 102 | }); 103 | 104 | it("if the input is empty commandHandle should not be called", function(){ 105 | expect(py_console.console_options.commandHandle('', report)).toEqual(''); 106 | expect(python_console_rest_api.sendPythonExpression).not.toHaveBeenCalled(); 107 | }); 108 | 109 | it("py_console.console_options.commandHandle with 'next' should call get_step and clean the console", function(){ 110 | py_console.init($, python_console_rest_api, window, utils); 111 | spyOn(py_console.console, 'reset').and.callThrough(); 112 | py_console.console_options.commandHandle('next', report); 113 | 114 | expect(py_console.get_step).toHaveBeenCalled(); 115 | expect(py_console.console.reset).toHaveBeenCalled(); 116 | expect(python_console_rest_api.sendPythonExpression).not.toHaveBeenCalled(); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /TryPython/core/templates/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Python Console 5 | 6 | {% load staticfiles %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 22 | {% csrf_token %} 23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /TryPython/core/tests/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'yori' 2 | -------------------------------------------------------------------------------- /TryPython/core/tests/test_ast_utils.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from core.ast_utils import isFunction 4 | 5 | 6 | class AstUtilsTestCase(unittest.TestCase): 7 | 8 | def test_name_binding_is_not_a_function(self): 9 | self.assertFalse(isFunction("a = 1")) 10 | 11 | def test_def_statement_is_function(self): 12 | self.assertTrue(isFunction('def f():\n return True\n')) 13 | 14 | def test_lambda_statement_is_function(self): 15 | self.assertTrue(isFunction('f = lambda x: x*x')) 16 | -------------------------------------------------------------------------------- /TryPython/core/tests/test_commands.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | from subprocess import CalledProcessError 4 | 5 | import six 6 | from six import StringIO 7 | 8 | if six.PY2: 9 | from mock import patch 10 | else: 11 | from unittest.mock import patch 12 | 13 | from django.conf import settings 14 | from django.test import TestCase 15 | from django.core.management import call_command 16 | 17 | 18 | class CommandsTests(TestCase): 19 | 20 | def setUp(self): 21 | super(self.__class__, self).setUp() 22 | self.namespace = json.dumps({}) 23 | self.fake_stdout = StringIO() 24 | self.fake_stderr = StringIO() 25 | self.original_stderr = sys.stderr 26 | self.original_stdout = sys.stdout 27 | sys.stderr = self.fake_stderr 28 | sys.stdout = self.fake_stdout 29 | 30 | def tearDown(self): 31 | super(self.__class__, self).tearDown() 32 | sys.stderr = self.original_stderr 33 | sys.stdout = self.original_stdout 34 | 35 | @patch('docker.Client') 36 | def test_eval_with_docker(self, docker_client): 37 | expression = '1+1' 38 | call_command('eval', '--with-docker', expression, self.namespace) 39 | 40 | docker_client.assert_called_with(base_url=settings.DOCKER_BASE_URL) 41 | 42 | @patch('subprocess.check_output') 43 | def test_subprocess_raises_called_error(self, check_output): 44 | check_output.side_effect = CalledProcessError(2, "") 45 | 46 | call_command('eval', '1+1', self.namespace) 47 | self.assertEqual( 48 | self.fake_stderr.getvalue(), 49 | "Command '' returned non-zero exit status 2") 50 | -------------------------------------------------------------------------------- /TryPython/core/tests/test_views.py: -------------------------------------------------------------------------------- 1 | import json 2 | from django.test import TestCase 3 | 4 | 5 | class ViewsTestCase(TestCase): 6 | 7 | def test_index_view(self): 8 | response = self.client.get("/") 9 | 10 | self.assertEquals(response.status_code, 200) 11 | self.assertEquals(response.template_name, ['main.html']) 12 | 13 | 14 | class EvalViewTestCase(TestCase): 15 | 16 | def test_eval_view_simple_expression(self): 17 | response = self.client.post("/eval", {"toEval": "1+1"}) 18 | expected = {"out": "2\n", "err": ""} 19 | decoded_response = json.loads(response.content.decode("utf-8")) 20 | 21 | self.assertEquals(response.status_code, 200) 22 | self.assertEquals(decoded_response, expected) 23 | 24 | def test_eval_view_name_binding(self): 25 | response = self.client.post("/eval", {"toEval": "a = 1"}) 26 | 27 | self.assertEquals(response.status_code, 200) 28 | 29 | response = self.client.post("/eval", {"toEval": "a"}) 30 | 31 | expected = {u'err': u'', u'out': u'1\n'} 32 | decoded_response = json.loads(response.content.decode('utf-8')) 33 | 34 | self.assertEquals(response.status_code, 200) 35 | self.assertEquals(decoded_response, expected) 36 | 37 | def test_eval_view_for_statement(self): 38 | response = self.client.post( 39 | "/eval", {"toEval": "for x in (1, 2):\n print(x)\n"}) 40 | expected = {u'err': u'', u'out': u'1\n2\n'} 41 | decoded_response = json.loads(response.content.decode("utf-8")) 42 | self.assertEquals(response.status_code, 200) 43 | self.assertEquals(decoded_response, expected) 44 | 45 | def test_eval_view_while_loop(self): 46 | response = self.client.post( 47 | "/eval", {"toEval": "while 1:\n print('hey')\n break\n"}) 48 | expected = {"out": "hey\n", "err": ""} 49 | decoded_response = json.loads(response.content.decode("utf-8")) 50 | 51 | self.assertEquals(response.status_code, 200) 52 | self.assertEquals(decoded_response, expected) 53 | 54 | def test_eval_view_that_will_raise_python_exception(self): 55 | response = self.client.post("/eval", {'toEval': 'a'}) 56 | expected = {"out": "", 57 | "err": "Traceback (most recent call last):\n" 58 | " File \"\"," 59 | " line 1, in \nNameError: name \'a\' is not" 60 | " defined\n" 61 | } 62 | decoded_response = json.loads(response.content.decode("utf-8")) 63 | 64 | self.assertEquals(response.status_code, 200) 65 | self.assertEquals(decoded_response, expected) 66 | 67 | def test_eval_view_function_statement(self): 68 | response = self.client.post( 69 | "/eval", {"toEval": 'def f():\n print("x")\n'}) 70 | self.assertEquals(response.status_code, 200) 71 | 72 | response = self.client.post("/eval", {"toEval": 'f()'}) 73 | expected = json.dumps({"out": "x\n", "err": ""}).encode("utf-8") 74 | 75 | self.assertEquals(response.content, expected) 76 | 77 | def test_eval_lambda_function(self): 78 | response = self.client.post("/eval", {"toEval": 'f = lambda x: x*x'}) 79 | 80 | self.assertEquals(response.status_code, 200) 81 | 82 | response = self.client.post("/eval", {"toEval": 'f(2)'}) 83 | expected = json.dumps({"out": "4\n", "err": ""}).encode("utf-8") 84 | 85 | self.assertEquals(response.content, expected) 86 | -------------------------------------------------------------------------------- /TryPython/core/views.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import json 4 | import six 5 | from six import StringIO 6 | 7 | from django.core.management import call_command 8 | from django.views.generic import TemplateView, View 9 | from django.http import JsonResponse 10 | 11 | if six.PY2: 12 | import ast_utils 13 | else: 14 | from . import ast_utils 15 | 16 | 17 | class IndexView(TemplateView): 18 | template_name = "main.html" 19 | 20 | 21 | class EvalView(View): 22 | 23 | def post(self, request): 24 | to_eval = request.POST.get("toEval") 25 | default_namespace_value = json.dumps({'functions': []}) 26 | namespace = json.loads(request.session.get('namespace', default_namespace_value)) 27 | out = StringIO() 28 | if ast_utils.isFunction(to_eval): 29 | namespace['functions'].append(to_eval) 30 | call_command("eval", '', json.dumps(namespace), stdout=out) 31 | else: 32 | call_command("eval", to_eval, json.dumps(namespace), stdout=out) 33 | values = json.loads(out.getvalue()) 34 | out, namespace = values['out'], values['namespace'] 35 | err = values['error'] 36 | request.session['namespace'] = namespace 37 | return JsonResponse({'out': out, 'err': err}) 38 | -------------------------------------------------------------------------------- /TryPython/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "TryPython.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /TryPython/tutorial/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'yori' 2 | -------------------------------------------------------------------------------- /TryPython/tutorial/admin.py: -------------------------------------------------------------------------------- 1 | import six 2 | from django.contrib import admin 3 | 4 | if six.PY2: 5 | from models import Step 6 | else: 7 | from .models import Step 8 | 9 | 10 | class StepAdmin(admin.ModelAdmin): 11 | pass 12 | 13 | admin.site.register(Step, StepAdmin) 14 | -------------------------------------------------------------------------------- /TryPython/tutorial/fixtures/steps.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "tutorial.Step", 4 | "pk": 1, 5 | "fields":{ 6 | "content": "This tutorial will guide you on ....", 7 | "title": "Learn python in 15 minutes" 8 | } 9 | } 10 | ] -------------------------------------------------------------------------------- /TryPython/tutorial/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='Step', 15 | fields=[ 16 | ('id', models.AutoField(verbose_name='ID', 17 | serialize=False, auto_created=True, primary_key=True)), 18 | ('title', models.TextField()), 19 | ('content', models.TextField()), 20 | ], 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /TryPython/tutorial/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinQuarantine/TryPython/358cb62fc0dab9da2a30e97b718888a366c4fd5c/TryPython/tutorial/migrations/__init__.py -------------------------------------------------------------------------------- /TryPython/tutorial/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Step(models.Model): 5 | title = models.TextField() 6 | content = models.TextField() 7 | 8 | def to_dict(self): 9 | return { 10 | 'content': self.content, 11 | 'title': self.title 12 | } 13 | -------------------------------------------------------------------------------- /TryPython/tutorial/tests/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'yori' 2 | -------------------------------------------------------------------------------- /TryPython/tutorial/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from tutorial.models import Step 3 | 4 | 5 | class ModelsTestCase(TestCase): 6 | 7 | def test_insert_step(self): 8 | step = Step(content="some content ...", title="Title") 9 | step.save() 10 | 11 | self.assertDictEqual( 12 | step.to_dict(), 13 | {'content': "some content ...", "title": "Title"}) 14 | -------------------------------------------------------------------------------- /TryPython/tutorial/tests/test_views.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from tutorial.models import Step 3 | 4 | 5 | class ViewsTestCase(TestCase): 6 | 7 | def test_step_view_that_doesnt_exists(self): 8 | response = self.client.post('/get_step', {'step': 1}) 9 | 10 | self.assertEquals(response.status_code, 404) 11 | 12 | def test_step_view_get_first_step(self): 13 | Step(content="some content ...", title="Title").save() 14 | response = self.client.post('/get_step', {'step': 1}) 15 | 16 | self.assertEquals(response.status_code, 200) 17 | -------------------------------------------------------------------------------- /TryPython/tutorial/views.py: -------------------------------------------------------------------------------- 1 | import six 2 | from django.views.generic import View 3 | from django.shortcuts import get_object_or_404 4 | from django.http.response import JsonResponse 5 | 6 | if six.PY2: 7 | from models import Step 8 | else: 9 | from .models import Step 10 | 11 | 12 | class StepView(View): 13 | 14 | def post(self, request): 15 | step_number = request.POST.get('step') 16 | step = get_object_or_404(Step, pk=int(step_number)) 17 | return JsonResponse(step.to_dict()) 18 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | RUN apt-get update && apt-get install -y python3 python3-pip 3 | RUN pip3 install six 4 | COPY eval.py eval.py 5 | ENTRYPOINT python3 eval.py $0 $1 6 | -------------------------------------------------------------------------------- /docker/eval.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import print_function 3 | import sys 4 | import types 5 | import code 6 | import json 7 | from contextlib2 import redirect_stderr, redirect_stdout 8 | 9 | from six import class_types, StringIO 10 | 11 | 12 | def _get_console_out(to_eval, namespace): 13 | fake_out, fake_err = StringIO(), StringIO() 14 | console = code.InteractiveConsole(locals=namespace) 15 | with redirect_stdout(fake_out), redirect_stderr(fake_err): 16 | for function in namespace.get("functions", []): 17 | for statement in function.split("\\n"): 18 | console.push(statement) 19 | for statement in to_eval.split("\n"): 20 | if statement: 21 | console.push(statement) 22 | else: 23 | console.push('\n') 24 | return fake_out.getvalue(), fake_err.getvalue() 25 | 26 | 27 | def eval_(to_eval, namespace=None): 28 | if namespace is None: 29 | namespace = {} 30 | else: 31 | namespace = eval(namespace) 32 | namespace['__builtins__'] = {'print': print} 33 | out, errors = _get_console_out(to_eval, namespace) 34 | new_namespace = {} 35 | for key, value in namespace.items(): 36 | if key != "__builtins__": 37 | if not (isinstance(value, types.FunctionType) or isinstance(value, class_types)): 38 | new_namespace[key] = value 39 | return {'out': out, 'namespace': json.dumps(new_namespace), 'error': errors} 40 | 41 | if __name__ == "__main__": 42 | to_eval = sys.argv[1] 43 | namespace = sys.argv[2] 44 | json.dump(eval_(to_eval, namespace=namespace), sys.stdout) 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Try-Python", 3 | "description": "Try Python on your browser", 4 | "author": { 5 | "name": "Iury Alves de Souza", 6 | "email": "iuryalves20@gmail.com" 7 | }, 8 | "scripts": { 9 | "build": "rollup -c", 10 | "watch": "rollup -c -w", 11 | "test": "jest" 12 | }, 13 | "devDependencies": { 14 | "@rollup/plugin-babel": "^5.1.0", 15 | "jest": "^26.1.0", 16 | "phantomjs": "^2.1.7", 17 | "rollup": "^2.23.0", 18 | "rollup-plugin-commonjs": "^10.1.0", 19 | "rollup-plugin-node-resolve": "^5.2.0", 20 | "rollup-plugin-terser": "^6.1.0" 21 | }, 22 | "dependencies": { 23 | "jquery": "^3.5.1", 24 | "serve": "^11.3.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /python-content/en/syllabus.txt: -------------------------------------------------------------------------------- 1 | 1 - Variables and input data 2 | 3.1 Variable names 3 | 3.2 Numeric variables 4 | 3.2.1 Numeric values represntation 5 | 3.3 Logic type variables 6 | 3.3.1 Relational operators 7 | 3.3.2 Logical operators 8 | 3.3.3 Logical expressions 9 | 3.4 String variables 10 | 3.4.1 Operaions with trings 11 | 3.5 Sequencies and time 12 | 3.6 Traceability 13 | 3.7 Data input 14 | 3.7.1 Data input conversion 15 | 3.7.2 Common errors 16 | 17 | 2 - Conditions 18 | 4.1 if 19 | 4.2 else 20 | 4.3 Nested structures 21 | 4.4 elif 22 | 23 | 3 - Repetitions 24 | 5.1 Counters 25 | 5.2 Acuumulators 26 | 5.3 Breaking a repetition 27 | 5.4 Nested repetitions 28 | 29 | 4 - Lists 30 | 6.1 Working with indexes 31 | 6.2 Copying and slicing lists 32 | 6.3 Size of lists 33 | 6.4 Adding elements 34 | 6.5 Removing elements from lists 35 | 6.6 Using lists as queues 36 | 6.7 Using lists as stacks 37 | 6.9 Research 38 | 6.10 Range 39 | 6.11 Enumerate 40 | 6.12 Operations with lists 41 | 6.13 Applications 42 | 6.14 Lists with strings 43 | 6.15 Lists inside lists 44 | 6.16 Ordering 45 | 6.17 Dictionaries 46 | 6.18 Dictionaries with lists 47 | 48 | 5 - Working with strings 49 | 7.1 Partial verification of strings 50 | 7.2 Counting 51 | 7.3 Strings research 52 | 7.4 Positioning of strings 53 | 7.5 Breaking or separating strings 54 | 7.6 Replacing strings 55 | 7.7 Removing blank spaces 56 | 7.8 Validating by content type 57 | 7.9 Formatting strings 58 | 7.9.1 Formatting numbers 59 | 7.10 Hangman game 60 | 61 | 6 - Functions 62 | 8.1 Local and global variables 63 | 8.2 Recursive functions 64 | 8.3 Validation 65 | 8.4 Optional arguments 66 | 8.5 Naming parameters 67 | 8.6 Functions as arguments 68 | 8.7 Packing and unpaking arguments 69 | 8.8 Unpacking arguments 70 | 8.9 Lambda functions 71 | 8.10 Modules 72 | 8.11 Random numbers 73 | 8.12 The type function 74 | 75 | 7 - Files 76 | 9.1 Command line arguments 77 | 9.2 Generating files 78 | 9.3 Reading and writing 79 | 9.4 Processing a file 80 | 9.5 Generating HTML 81 | 9.6 Files and directories 82 | 9.7 Something about time 83 | 9.8 Using paths 84 | 9.9 Visiting every directory recursively 85 | 86 | 8 - Classes and objects 87 | 10.1 Objects as a representation of the real world 88 | 10.2 Passing arguments 89 | 10.3 Bank example 90 | 10.4 Inheritance 91 | 92 | 9 - Next steps 93 | 11.1 Functional programming 94 | 11.2 Algorithms 95 | 11.3 Games 96 | 11.4 Object orientation 97 | 11.5 Database 98 | 11.6 Web systems 99 | 11.7 Other Python libraries 100 | 11.8 Discussion lists 101 | 102 | Appendix A - Error messages 103 | A.1 SyntaxError 104 | A.2 IdentationError 105 | A.3 KeyError 106 | A.4 NameError 107 | A.5 ValueError 108 | A.6 TypeError 109 | A.7 IndexError -------------------------------------------------------------------------------- /python-content/pt-br/syllabus.txt: -------------------------------------------------------------------------------- 1 | 1 - Variáveis e entrada de dados 2 | 3.1 Nomes de variáveis 3 | 3.2 Variáveis numéricas 4 | 3.2.1 Representação de valores numéricos 5 | 3.3 Variáveis do tipo lógico 6 | 3.3.1 Operadores relacionais 7 | 3.3.2 Operadores lógicos 8 | 3.3.3 Expressões lógicas 9 | 3.4 Variáveis string 10 | 3.4.1 Operações com strings 11 | 3.5 Sequências e tempo 12 | 3.6 Rastreamento 13 | 3.7 Entrada de dados 14 | 3.7.1 Conversão da entrada de dados 15 | 3.7.2 Erros comuns 16 | 17 | 2 - Condições 18 | 4.1 if 19 | 4.2 else 20 | 4.3 Estruturas aninhadas 21 | 4.4 elif 22 | 23 | 3 - Repetições 24 | 5.1 Contadores 25 | 5.2 Acumuladores 26 | 5.3 Interrompendo a repetição 27 | 5.4 Repetições aninhadas 28 | 29 | 4 - Listas 30 | 6.1 Trabalhando com índices 31 | 6.2 Cópia e fatiamento de listas 32 | 6.3 Tamanho de listas 33 | 6.4 Adição de elementos 34 | 6.5 Remoção de elementos da lista 35 | 6.6 Usando listas como filas 36 | 6.7 Uso de listas como pilhas 37 | 6.9 Pesquisa 38 | 6.10 Range 39 | 6.11 Enumerate 40 | 6.12 Operações com listas 41 | 6.13 Aplicações 42 | 6.14 Listas com Strings 43 | 6.15 Listas dentro de listas 44 | 6.16 Ordenação 45 | 6.17 Dicionários 46 | 6.18 Dicionários com listas 47 | 48 | 5 - Trabalhando com strings 49 | 7.1 Verificação parcial de strings 50 | 7.2 Contagem 51 | 7.3 Pesquisa de strings 52 | 7.4 Posicionamento de strings 53 | 7.5 Quebra ou separação de strings 54 | 7.6 Substituição de strings 55 | 7.7 Remoção de espaços em branco 56 | 7.8 Validação por tipo de conteúdo 57 | 7.9 Formatação de strings 58 | 7.9.1 Formatação de números 59 | 7.10 Jogo da forca 60 | 61 | 6 - Funções 62 | 8.1 Variáveis locais e globais 63 | 8.2 Funções recursivas 64 | 8.3 Validação 65 | 8.4 Parâmetros opcionais 66 | 8.5 Nomeando parâmetros 67 | 8.6 Funções como parâmetro 68 | 8.7 Empacotamento e desempacotamento de parâmetros 69 | 8.8 Desempacotamento de parâmetros 70 | 8.9 Funções Lambda 71 | 8.10 Módulos 72 | 8.11 Números aleatórios 73 | 8.12 A função type 74 | 75 | 7 - Arquivos 76 | 9.1 Parâmetros da linha de comando 77 | 9.2 Geração de arquivos 78 | 9.3 Leitura e escrita 79 | 9.4 Processamento de um arquivo 80 | 9.5 Geração de HTML 81 | 9.6 Arquivos e diretórios 82 | 9.7 Um pouco sobre o tempo 83 | 9.8 Uso de caminhos 84 | 9.9 Visita a todos os subdiretórios recursivamente 85 | 86 | 8 - Clases e objetos 87 | 10.1 Objetos como representação do mundo real 88 | 10.2 Passagem de parâmetros 89 | 10.3 O exemplo de um banco 90 | 10.4 Herança 91 | 92 | 9 - Próximos passos 93 | 11.1 Programação funcional 94 | 11.2 Algoritmos 95 | 11.3 Jogos 96 | 11.4 Orientação a objetos 97 | 11.5 Banco de dados 98 | 11.6 Sistemas web 99 | 11.7 Outras bibliotecas Python 100 | 11.8 Listas de discussão 101 | 102 | Apêndice A - Mensagens de erro 103 | A.1 SyntaxError 104 | A.2 IdentationError 105 | A.3 KeyError 106 | A.4 NameError 107 | A.5 ValueError 108 | A.6 TypeError 109 | A.7 IndexError -------------------------------------------------------------------------------- /requirements/dev_requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | pep8==1.6.2 3 | tox==2.1.1 4 | coveralls==1.1 5 | coverage==4.0.3 6 | mock==1.3.0 7 | -------------------------------------------------------------------------------- /requirements/production_requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | newrelic==2.60.0.46 4 | -------------------------------------------------------------------------------- /requirements/requirements.txt: -------------------------------------------------------------------------------- 1 | django==1.11.29 2 | python-decouple==3.0 3 | six==1.10.0 4 | docker-py==1.7.0 5 | contextlib2==0.5.1 6 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel' 2 | import { terser } from 'rollup-plugin-terser' 3 | import resolve from 'rollup-plugin-node-resolve' 4 | import commonJS from 'rollup-plugin-commonjs' 5 | 6 | const production = !process.env.ROLLUP_WATCH 7 | 8 | export default { 9 | input: 'TryPython/core/static/js/src/index.js', 10 | output: { 11 | file: 'TryPython/core/static/js/dist/scripts.js', 12 | format: 'iife', 13 | }, 14 | plugins: [ 15 | resolve(), 16 | commonJS({ 17 | include: 'node_modules/**', 18 | }), 19 | babel({ babelHelpers: 'bundled' }), 20 | production && terser(), 21 | ], 22 | } 23 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py27,py34, pep8 3 | skipsdist=True 4 | 5 | [testenv] 6 | passenv=DJANGO_SECRET_KEY 7 | deps=-r{toxinidir}/requirements/dev_requirements.txt 8 | commands= 9 | python TryPython/manage.py migrate 10 | coverage run TryPython/manage.py test core tutorial 11 | 12 | [testenv:pep8] 13 | deps=-r{toxinidir}/requirements/dev_requirements.txt 14 | commands=pep8 {toxinidir} --max-line-length=120 --ignore=E402 --count 15 | -------------------------------------------------------------------------------- /try-python.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuentinQuarantine/TryPython/358cb62fc0dab9da2a30e97b718888a366c4fd5c/try-python.gif --------------------------------------------------------------------------------