├── .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 [](https://travis-ci.org/IuryAlves/TryPython) [](https://coveralls.io/github/IuryAlves/TryPython?branch=master) [](http://forthebadge.com/images/badges/built-with-love.svg)
2 |
3 | 
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 |
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 | '
'
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 | '' +
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 |
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
--------------------------------------------------------------------------------