<commit_before>#!/usr/bin/python3
# -*- coding: utf-8
"""Letscode is just a language-aware tool to quickly perform a bunch of usual
actions on a piece of code.

Letscode provides some kind of convenient abstraction over the different
languages you are working with and the related tools (compiler, interpreter,
etc). You might not want to remember how to write a simple hello-world, compile,
build the documentation, launch your tests, start the static analysis or just
run your code for all the languages you are working with. Also, it provides a
quick access to the relevant offline/online documents.

Letscode will guess what your code file is all about and do things for you.
For instance, in Vim, "letscode.py % -a compile" will lauch letscode on the
current file, detect its language and compile it.

The different languages derive from a common class Language. Adding a new
language is pretty straightforward, one just need to inherit from Language
and override the name and extensions attributes. Then, the different
actions can be defined via class methods. Code can be re-used by inheriting
from classes implementing usual behaviors such as ScriptingLanguage or
CompiledLanguages.
"""

# Ideas : Add option to tell whether we want to build stuff when
# we want to run and output file does not exist

# When code can be run or compiled, check if compiled version exists and if
# it is older or newer

# Use meta-stuff to get the list of actions automatically

# Check if multiple actions can be added in one-go : -a compile,run

# Add a way to pass additional parameter to command-line

# Add default actions (create?)

# Next steps are :
# - source control
# - refactoring (splitting into files) + adding unit tests

import argparse
import subprocess
import os
import stat
import shutil
import unittest
import re
import tempfile
from collections import namedtuple
from textwrap import dedent


def print_info(*objs):
    """Prints INFO level messages"""
    # print('INFO:', *objs, file=sys.stdout)
    print('INFO: %s' % (objs))


def print_error(*objs):
    """Prints ERROR level messages"""
    # print('ERROR:', *objs, file=sys.stderr)
    print('ERROR: %s' % (objs))


def print_warning(*objs):
    """Prints WARNING level messages"""
    # print('WARNING:', *objs, file=sys.stderr)
    print('WARNING: %s' % (objs))


def print_debug(*objs):
    """Prints DEBUG level messages"""
    print('DEBUG: %s' % (objs))


def subprocess_call_wrapper(lst, stdin=None):
    """Wrapper around the subprocess.call functions."""
    print_debug('About to run "%s"' % ' '.join(lst))
    try:
        ret = subprocess.call(lst, stdin=stdin)
    except (OSError, IOError):
        ret = 127  # an error code
    except IndexError:
        ret = 127  # an error code
    print_debug('Command "%s" returned %d' % (lst[0] if lst else '', ret))
    return ret == 0


class Language(object):
    """A generic class for programming languages"""
    name = None
    extensions = None
    information = None

    @classmethod
    def function_not_implemented(cls, function):
        """Default behavior for non-implemented functions."""
        print_error('%s not implemented for %s' % (function, cls.name))
        return False

    @classmethod
    def perform_actions(cls, args):
        """Perform the actions on the file - entry point for the class"""
        assert args.failure in ['stop', 'continue']
        stop_on_failure = args.failure == 'stop'
        results = []
        for action in args.action:
            ret = cls.perform_action(action, args)
            results.append((action, ret))
            if not ret and stop_on_failure:
                break
        # This should be moved outside the function - refactoring required
        greentick = '\033[92m✔'
        redcross = '\033[91m✘'
        undocolor = '\033[0m'
        for action, ret in results:
            print_info((greentick if ret else redcross) + undocolor +
                ' ' + action)
        return all(res for _, res in results)

    @classmethod
    def perform_action(cls, action, args):
        """Perform a single on the file."""
        if hasattr(cls, action):
            return getattr(cls, action)(args)
        else:
            return cls.function_not_implemented(action)

    @classmethod
    def info(cls, _):
        """Gets information about the language. Wrapper around the information
            class member performing some pretty printing."""
        if cls.information is None:
            return cls.function_not_implemented('info')
        assert cls.name is not None
        corner, side, top = '+', '|', '-'
        title = ' Information about ' + cls.name + ' '
        line = corner + top * len(title) + corner
        print('\n'.join([line, side + title + side, line, cls.information]))
        return True

    @classmethod
    def get_actual_filename_to_use(cls, args):
        """Returns the filename to be used based on the name and an eventual
        extension."""
        filename, extmode = args.filename, args.extension_mode
        assert extmode in ['auto', 'never', 'always']

        # if extension mode is auto, we check if the extension is required
        # (if it is not in the list of extensions) and fallback to always/never
        if extmode == 'auto':
            extmode = 'never' if (os.path.splitext(filename)[1].lower() in ("." + e for e in cls.extensions)) else 'always'
        assert extmode in ['never', 'always']

        # if extension is required, add the first one
        if extmode == 'always':
            filename += "." + cls.extensions[0]

        return filename

    @classmethod
    def create(cls, args):
        """Creates and ensures readiness of a file (shebang, boiler-plate code,
        execution rights, etc). Wrapper around real_create."""
        filename = cls.get_actual_filename_to_use(args)
        if os.path.isfile(filename):
            assert(args.override_file in ['n', 'y'])
            if args.override_file == 'n':
                print_info("File %s already exists" % filename)
                return True
        try:
            cls.real_create(filename)
        except IOError:
            print_error("Error while creating file" % filename)
            return False
        return True

    @classmethod
    def real_create(cls, filename):
        """Creates and ensures readiness of a file (shebang, boiler-plate code,
        execution rights, etc)."""
        with open(filename, 'w') as filed:
            filed.write(cls.get_file_content(filename))

    @classmethod
    def get_file_content(cls, _):
        """Returns the content to be put in the file."""
        return ""


class CompiledLanguages(Language):
    """A generic class for compiled languages"""
    compiler = None
    compiler_options = []

    @classmethod
    def man(cls, _):
        """Gets the manual"""
        return subprocess_call_wrapper(['man', cls.compiler])

    @classmethod
    def get_output_filename(cls, filename):
        """Gets the name of the output file"""
        return os.path.splitext(filename)[0] + '_out'

    @classmethod
    def compile(cls, args):
        """Compiles the file"""
        filename = cls.get_actual_filename_to_use(args)
        output = cls.get_output_filename(filename)
        return subprocess_call_wrapper(
            [cls.compiler, filename, '-o', output] + cls.compiler_options)

    @classmethod
    def run(cls, args):
        """Runs the code"""
        # We do not look in '.' by default, let's use the abs path
        output = os.path.abspath(
            cls.get_output_filename(
                cls.get_actual_filename_to_use(args)))
        return subprocess_call_wrapper([output])


class CLanguage(CompiledLanguages):
    """C"""
    name = 'c'
    code_extensions = ['c']
    header_extensions = ['h']
    extensions = code_extensions + header_extensions
    compiler = 'gcc'
    compiler_options = ['-Wall', '-Wextra', '-std=c99']
    information = dedent('''
- Wikipedia page : http://en.wikipedia.org/wiki/C_%28programming_language%29
- Official site :
- Documentation :
    * FAQ http://www.c-faq.com/
- Subreddit : http://www.reddit.com/r/C_Programming/
- Tools online :
    * Compiler (with ASM output - no run) http://gcc.godbolt.org/
    * Compiler (with ASM output - no run) http://assembly.ynh.io/
    * C gibberish <-> English http://cdecl.org/
    * Demangler : http://demangler.com/
    * Online compiler (run) http://coliru.stacked-crooked.com/
- RosettaCode : http://rosettacode.org/wiki/Category:C
''')

    @classmethod
    def debug(cls, args):
        """Launches the debugger"""
        output = cls.get_output_filename(cls.get_actual_filename_to_use(args))
        return subprocess_call_wrapper(['gdb', output])

    @classmethod
    def pretty(cls, args):
        """Makes the code prettier"""
        filename = cls.get_actual_filename_to_use(args)
        # or maybe using astyle
        return subprocess_call_wrapper(['indent', filename])

    @classmethod
    def get_header_content(cls, symbol):
        """Gets code for a header file"""
        return dedent('''
            #ifndef %s
            #define %s
            #endif
            ''') % (symbol, symbol)

    @classmethod
    def get_code_content(cls, included):
        """Gets code for an implementation file"""
        return dedent('''
            #include <stdio.h>
            //#include "%s"

            int main(int argc, char* argv[])
            {
                printf("Hello, world!\\n");
                return 0;
            }
            ''') % included

    @classmethod
    def get_file_content(cls, filename):
        """Returns the content to be put in the file."""
        # Different file content for implementations files and headers
        realfilename = os.path.split(filename)[1]
        base, ext = os.path.splitext(realfilename)
        if ext in ('.' + e for e in cls.header_extensions):
            return cls.get_header_content(
                '__%s__' % re.sub('[^A-Z]', '_', base.upper()))
        else:
            assert ext in ('.' + e for e in cls.code_extensions)
            return cls.get_code_content(base + '.' + cls.header_extensions[0])

    @classmethod
    def check(cls, args):
        """Calls static checker"""
        filename = cls.get_actual_filename_to_use(args)
        commands = {
            'cppcheck': ['--enable=all']
        }
        return_values = [
            subprocess_call_wrapper([c] + opt + [filename])
            for c, opt in commands.items()]
        return all(return_values)

    @classmethod
    def metrics(cls, args):
        """Gets metrics for code"""
        # to be done cppncss and cccc
        filename = cls.get_actual_filename_to_use(args)
        return subprocess_call_wrapper(['c_count', filename])


class Cpp(CLanguage):
    """Cpp"""
    name = 'cpp'
    code_extensions = ['cpp', 'cc', 'cxx', 'c++']
    header_extensions = ['hpp', 'hh', 'h', 'hxx', 'h++']
    extensions = code_extensions + header_extensions
    compiler = 'g++'
    compiler_options = ['-Wall', '-Wextra']

    information = dedent('''
- Wikipedia page : http://en.wikipedia.org/wiki/C++
- Official site : http://isocpp.org/
- Documentation : http://en.cppreference.com/
- Misc :
    * Guru of the week http://www.gotw.ca/gotw/
- Subreddit : http://www.reddit.com/r/cpp/
- Tools online :
    * Compiler (with ASM output - no run) http://gcc.godbolt.org/
    * Compiler (with ASM output - no run) http://assembly.ynh.io/
    * C gibberish <-> English http://cdecl.org/
    * Demangler : http://demangler.com/
    * Online compiler (run) http://coliru.stacked-crooked.com/
- RosettaCode : http://rosettacode.org/wiki/Category:C%2B%2B
''')

    @classmethod
    def get_code_content(cls, included):
        """Gets code for an implementation file"""
        return dedent('''
            #include <iostream>
            //#include "%s"

            int main(int argc, char* argv[])
            {
                std::cout << "Hello, world!" << std::endl;
                return 0;
            }
            ''') % included


class Java(CompiledLanguages):
    """Java"""
    name = 'java'
    extensions = ['java', 'class', 'jar']
    compiler = 'javac'  # support for gcj could be added if needed

    information = dedent('''
- Wikipedia page : http://en.wikipedia.org/wiki/Java_%28programming_language%29
- Official site : http://www.java.com/
- Documentation : http://docs.oracle.com/javase/7/docs/api/
- Subreddit : http://www.reddit.com/r/java/
- Tools online :
    * Visualiser : http://cscircles.cemc.uwaterloo.ca/java_visualize/
    * Demangler : http://demangler.com/
    * REPL : http://www.javarepl.com/console.html
- RosettaCode : http://rosettacode.org/wiki/Category:Java
- Misc ressources :
    * Hidden features (StackOverflow) : http://stackoverflow.com/questions/15496/hidden-features-of-java
    ''')

    @classmethod
    def get_output_filename(cls, filename):
        """Gets the name of the output file"""
        return cls.get_classfile(filename) + '.class'

    @classmethod
    def get_classfile(cls, filename):
        """Gets the name of the file without extensions (name of the class)"""
        return os.path.splitext(filename)[0]

    @classmethod
    def get_file_content(cls, filename):
        """Returns the content to be put in the file."""
        classname = os.path.split(cls.get_classfile(filename))[1]
        return dedent('''
            public class %s {
                public static void main(String[] args) {
                    System.out.println("Hello, world!");
                }
            }''') % classname

    @classmethod
    def compile(cls, args):
        """Compiles the file"""
        filename = cls.get_actual_filename_to_use(args)
        return subprocess_call_wrapper(
            [cls.compiler, filename] + cls.compiler_options)

    @classmethod
    def run(cls, args):
        """Runs the code"""
        classfile = cls.get_classfile(cls.get_actual_filename_to_use(args))
        classpath, classname = os.path.split(classfile)
        return subprocess_call_wrapper(
            ['java', '-classpath', classpath, classname])

    @classmethod
    def debug(cls, args):
        """Launches the debugger"""
        classfile = cls.get_classfile(cls.get_actual_filename_to_use(args))
        classpath, classname = os.path.split(classfile)
        return subprocess_call_wrapper(
            ['jdb', '-classpath', classpath, classname])


class Fortran(CompiledLanguages):
    """Fortran"""
    name = 'fortran'
    extensions = ['f', 'for', 'f90', 'f95']
    compiler = 'gfortran'
    compiler_options = ['--free-form']

    information = dedent('''
- Wikipedia page : http://en.wikipedia.org/wiki/Fortran
- Official site : 
- Documentation : http://www.fortran90.org/
- Subreddit : http://www.reddit.com/r/fortran/ 
- Tools online :
- RosettaCode : http://rosettacode.org/wiki/Category:Fortran
    ''')

    @classmethod
    def get_file_content(cls, _):
        """Returns the content to be put in the file."""
        return dedent('''
            program helloworld
            print *, "Hello, world!"
            end program helloworld
            ''')

    @classmethod
    def debug(cls, args):
        """Launches the debugger"""
        output = cls.get_output_filename(cls.get_actual_filename_to_use(args))
        return subprocess_call_wrapper(['gdb', output])

    @classmethod
    def metrics(cls, args):
        """Gets metrics for code"""
        filename = cls.get_actual_filename_to_use(args)
        return subprocess_call_wrapper(['fortran_count', filename])


class Haskell(CompiledLanguages):
    """Haskell"""
    name = 'haskell'
    extensions = ['hs', 'lhs']
    compiler = 'ghc'
    information = dedent('''
- Wikipedia page : http://en.wikipedia.org/wiki/Haskell_%28programming_language%29
- Official site : http://www.haskell.org/
- Documentation : http://www.haskell.org/ghc/docs/7.6-latest/html/libraries/index.html
- Subreddit : http://www.reddit.com/r/haskell/
- Tools online :
    * Try Haskell : http://tryhaskell.org/
- RosettaCode : http://rosettacode.org/wiki/Category:Haskell
''')

    @classmethod
    def get_file_content(cls, _):
        """Returns the content to be put in the file."""
        return dedent('''
            main = putStrLn "Hello, world!"
            ''')

    @classmethod
    def metrics(cls, args):
        """Gets metrics for code"""
        filename = cls.get_actual_filename_to_use(args)
        return subprocess_call_wrapper(['haskell_count', filename])


class MarkupLanguage(Language):
    """A generic class for markup languages"""


class HTML(MarkupLanguage):
    """HTML"""
    name = 'html'
    extensions = ['html']
    information = dedent('''
- Wikipedia page : http://en.wikipedia.org/wiki/HTML
- Official site : http://www.w3.org/
- Subreddit : http://www.reddit.com/r/html
- Tools online :
    * W3C Validator : http://validator.w3.org/
''')

    @classmethod
    def pretty(cls, args):
        """Makes the code prettier"""
        filename = cls.get_actual_filename_to_use(args)<commit_msg>Enhance support for Haskell<commit_after>#!/usr/bin/python3
# -*- coding: utf-8
"""Letscode is just a language-aware tool to quickly perform a bunch of usual
actions on a piece of code.

Letscode provides some kind of convenient abstraction over the different
languages you are working with and the related tools (compiler, interpreter,
etc). You might not want to remember how to write a simple hello-world, compile,
build the documentation, launch your tests, start the static analysis or just
run your code for all the languages you are working with. Also, it provides a
quick access to the relevant offline/online documents.

Letscode will guess what your code file is all about and do things for you.
For instance, in Vim, "letscode.py % -a compile" will lauch letscode on the
current file, detect its language and compile it.

The different languages derive from a common class Language. Adding a new
language is pretty straightforward, one just need to inherit from Language
and override the name and extensions attributes. Then, the different
actions can be defined via class methods. Code can be re-used by inheriting
from classes implementing usual behaviors such as ScriptingLanguage or
CompiledLanguages.
"""

# Ideas : Add option to tell whether we want to build stuff when
# we want to run and output file does not exist

# When code can be run or compiled, check if compiled version exists and if
# it is older or newer

# Use meta-stuff to get the list of actions automatically

# Check if multiple actions can be added in one-go : -a compile,run

# Add a way to pass additional parameter to command-line

# Add default actions (create?)

# Next steps are :
# - source control
# - refactoring (splitting into files) + adding unit tests

import argparse
import subprocess
import os
import stat
import shutil
import unittest
import re
import tempfile
from collections import namedtuple
from textwrap import dedent


def print_info(*objs):
    """Prints INFO level messages"""
    # print('INFO:', *objs, file=sys.stdout)
    print('INFO: %s' % (objs))


def print_error(*objs):
    """Prints ERROR level messages"""
    # print('ERROR:', *objs, file=sys.stderr)
    print('ERROR: %s' % (objs))


def print_warning(*objs):
    """Prints WARNING level messages"""
    # print('WARNING:', *objs, file=sys.stderr)
    print('WARNING: %s' % (objs))


def print_debug(*objs):
    """Prints DEBUG level messages"""
    print('DEBUG: %s' % (objs))


def subprocess_call_wrapper(lst, stdin=None):
    """Wrapper around the subprocess.call functions."""
    print_debug('About to run "%s"' % ' '.join(lst))
    try:
        ret = subprocess.call(lst, stdin=stdin)
    except (OSError, IOError):
        ret = 127  # an error code
    except IndexError:
        ret = 127  # an error code
    print_debug('Command "%s" returned %d' % (lst[0] if lst else '', ret))
    return ret == 0


class Language(object):
    """A generic class for programming languages"""
    name = None
    extensions = None
    information = None

    @classmethod
    def function_not_implemented(cls, function):
        """Default behavior for non-implemented functions."""
        print_error('%s not implemented for %s' % (function, cls.name))
        return False

    @classmethod
    def perform_actions(cls, args):
        """Perform the actions on the file - entry point for the class"""
        assert args.failure in ['stop', 'continue']
        stop_on_failure = args.failure == 'stop'
        results = []
        for action in args.action:
            ret = cls.perform_action(action, args)
            results.append((action, ret))
            if not ret and stop_on_failure:
                break
        # This should be moved outside the function - refactoring required
        greentick = '\033[92m✔'
        redcross = '\033[91m✘'
        undocolor = '\033[0m'
        for action, ret in results:
            print_info((greentick if ret else redcross) + undocolor +
                ' ' + action)
        return all(res for _, res in results)

    @classmethod
    def perform_action(cls, action, args):
        """Perform a single on the file."""
        if hasattr(cls, action):
            return getattr(cls, action)(args)
        else:
            return cls.function_not_implemented(action)

    @classmethod
    def info(cls, _):
        """Gets information about the language. Wrapper around the information
            class member performing some pretty printing."""
        if cls.information is None:
            return cls.function_not_implemented('info')
        assert cls.name is not None
        corner, side, top = '+', '|', '-'
        title = ' Information about ' + cls.name + ' '
        line = corner + top * len(title) + corner
        print('\n'.join([line, side + title + side, line, cls.information]))
        return True

    @classmethod
    def get_actual_filename_to_use(cls, args):
        """Returns the filename to be used based on the name and an eventual
        extension."""
        filename, extmode = args.filename, args.extension_mode
        assert extmode in ['auto', 'never', 'always']

        # if extension mode is auto, we check if the extension is required
        # (if it is not in the list of extensions) and fallback to always/never
        if extmode == 'auto':
            extmode = 'never' if (os.path.splitext(filename)[1].lower() in ("." + e for e in cls.extensions)) else 'always'
        assert extmode in ['never', 'always']

        # if extension is required, add the first one
        if extmode == 'always':
            filename += "." + cls.extensions[0]

        return filename

    @classmethod
    def create(cls, args):
        """Creates and ensures readiness of a file (shebang, boiler-plate code,
        execution rights, etc). Wrapper around real_create."""
        filename = cls.get_actual_filename_to_use(args)
        if os.path.isfile(filename):
            assert(args.override_file in ['n', 'y'])
            if args.override_file == 'n':
                print_info("File %s already exists" % filename)
                return True
        try:
            cls.real_create(filename)
        except IOError:
            print_error("Error while creating file" % filename)
            return False
        return True

    @classmethod
    def real_create(cls, filename):
        """Creates and ensures readiness of a file (shebang, boiler-plate code,
        execution rights, etc)."""
        with open(filename, 'w') as filed:
            filed.write(cls.get_file_content(filename))

    @classmethod
    def get_file_content(cls, _):
        """Returns the content to be put in the file."""
        return ""


class CompiledLanguages(Language):
    """A generic class for compiled languages"""
    compiler = None
    compiler_options = []

    @classmethod
    def man(cls, _):
        """Gets the manual"""
        return subprocess_call_wrapper(['man', cls.compiler])

    @classmethod
    def get_output_filename(cls, filename):
        """Gets the name of the output file"""
        return os.path.splitext(filename)[0] + '_out'

    @classmethod
    def compile(cls, args):
        """Compiles the file"""
        filename = cls.get_actual_filename_to_use(args)
        output = cls.get_output_filename(filename)
        return subprocess_call_wrapper(
            [cls.compiler, filename, '-o', output] + cls.compiler_options)

    @classmethod
    def run(cls, args):
        """Runs the code"""
        # We do not look in '.' by default, let's use the abs path
        output = os.path.abspath(
            cls.get_output_filename(
                cls.get_actual_filename_to_use(args)))
        return subprocess_call_wrapper([output])


class CLanguage(CompiledLanguages):
    """C"""
    name = 'c'
    code_extensions = ['c']
    header_extensions = ['h']
    extensions = code_extensions + header_extensions
    compiler = 'gcc'
    compiler_options = ['-Wall', '-Wextra', '-std=c99']
    information = dedent('''
- Wikipedia page : http://en.wikipedia.org/wiki/C_%28programming_language%29
- Official site :
- Documentation :
    * FAQ http://www.c-faq.com/
- Subreddit : http://www.reddit.com/r/C_Programming/
- Tools online :
    * Compiler (with ASM output - no run) http://gcc.godbolt.org/
    * Compiler (with ASM output - no run) http://assembly.ynh.io/
    * C gibberish <-> English http://cdecl.org/
    * Demangler : http://demangler.com/
    * Online compiler (run) http://coliru.stacked-crooked.com/
- RosettaCode : http://rosettacode.org/wiki/Category:C
''')

    @classmethod
    def debug(cls, args):
        """Launches the debugger"""
        output = cls.get_output_filename(cls.get_actual_filename_to_use(args))
        return subprocess_call_wrapper(['gdb', output])

    @classmethod
    def pretty(cls, args):
        """Makes the code prettier"""
        filename = cls.get_actual_filename_to_use(args)
        # or maybe using astyle
        return subprocess_call_wrapper(['indent', filename])

    @classmethod
    def get_header_content(cls, symbol):
        """Gets code for a header file"""
        return dedent('''
            #ifndef %s
            #define %s
            #endif
            ''') % (symbol, symbol)

    @classmethod
    def get_code_content(cls, included):
        """Gets code for an implementation file"""
        return dedent('''
            #include <stdio.h>
            //#include "%s"

            int main(int argc, char* argv[])
            {
                printf("Hello, world!\\n");
                return 0;
            }
            ''') % included

    @classmethod
    def get_file_content(cls, filename):
        """Returns the content to be put in the file."""
        # Different file content for implementations files and headers
        realfilename = os.path.split(filename)[1]
        base, ext = os.path.splitext(realfilename)
        if ext in ('.' + e for e in cls.header_extensions):
            return cls.get_header_content(
                '__%s__' % re.sub('[^A-Z]', '_', base.upper()))
        else:
            assert ext in ('.' + e for e in cls.code_extensions)
            return cls.get_code_content(base + '.' + cls.header_extensions[0])

    @classmethod
    def check(cls, args):
        """Calls static checker"""
        filename = cls.get_actual_filename_to_use(args)
        commands = {
            'cppcheck': ['--enable=all']
        }
        return_values = [
            subprocess_call_wrapper([c] + opt + [filename])
            for c, opt in commands.items()]
        return all(return_values)

    @classmethod
    def metrics(cls, args):
        """Gets metrics for code"""
        # to be done cppncss and cccc
        filename = cls.get_actual_filename_to_use(args)
        return subprocess_call_wrapper(['c_count', filename])


class Cpp(CLanguage):
    """Cpp"""
    name = 'cpp'
    code_extensions = ['cpp', 'cc', 'cxx', 'c++']
    header_extensions = ['hpp', 'hh', 'h', 'hxx', 'h++']
    extensions = code_extensions + header_extensions
    compiler = 'g++'
    compiler_options = ['-Wall', '-Wextra']

    information = dedent('''
- Wikipedia page : http://en.wikipedia.org/wiki/C++
- Official site : http://isocpp.org/
- Documentation : http://en.cppreference.com/
- Misc :
    * Guru of the week http://www.gotw.ca/gotw/
- Subreddit : http://www.reddit.com/r/cpp/
- Tools online :
    * Compiler (with ASM output - no run) http://gcc.godbolt.org/
    * Compiler (with ASM output - no run) http://assembly.ynh.io/
    * C gibberish <-> English http://cdecl.org/
    * Demangler : http://demangler.com/
    * Online compiler (run) http://coliru.stacked-crooked.com/
- RosettaCode : http://rosettacode.org/wiki/Category:C%2B%2B
''')

    @classmethod
    def get_code_content(cls, included):
        """Gets code for an implementation file"""
        return dedent('''
            #include <iostream>
            //#include "%s"

            int main(int argc, char* argv[])
            {
                std::cout << "Hello, world!" << std::endl;
                return 0;
            }
            ''') % included


class Java(CompiledLanguages):
    """Java"""
    name = 'java'
    extensions = ['java', 'class', 'jar']
    compiler = 'javac'  # support for gcj could be added if needed

    information = dedent('''
- Wikipedia page : http://en.wikipedia.org/wiki/Java_%28programming_language%29
- Official site : http://www.java.com/
- Documentation : http://docs.oracle.com/javase/7/docs/api/
- Subreddit : http://www.reddit.com/r/java/
- Tools online :
    * Visualiser : http://cscircles.cemc.uwaterloo.ca/java_visualize/
    * Demangler : http://demangler.com/
    * REPL : http://www.javarepl.com/console.html
- RosettaCode : http://rosettacode.org/wiki/Category:Java
- Misc ressources :
    * Hidden features (StackOverflow) : http://stackoverflow.com/questions/15496/hidden-features-of-java
    ''')

    @classmethod
    def get_output_filename(cls, filename):
        """Gets the name of the output file"""
        return cls.get_classfile(filename) + '.class'

    @classmethod
    def get_classfile(cls, filename):
        """Gets the name of the file without extensions (name of the class)"""
        return os.path.splitext(filename)[0]

    @classmethod
    def get_file_content(cls, filename):
        """Returns the content to be put in the file."""
        classname = os.path.split(cls.get_classfile(filename))[1]
        return dedent('''
            public class %s {
                public static void main(String[] args) {
                    System.out.println("Hello, world!");
                }
            }''') % classname

    @classmethod
    def compile(cls, args):
        """Compiles the file"""
        filename = cls.get_actual_filename_to_use(args)
        return subprocess_call_wrapper(
            [cls.compiler, filename] + cls.compiler_options)

    @classmethod
    def run(cls, args):
        """Runs the code"""
        classfile = cls.get_classfile(cls.get_actual_filename_to_use(args))
        classpath, classname = os.path.split(classfile)
        return subprocess_call_wrapper(
            ['java', '-classpath', classpath, classname])

    @classmethod
    def debug(cls, args):
        """Launches the debugger"""
        classfile = cls.get_classfile(cls.get_actual_filename_to_use(args))
        classpath, classname = os.path.split(classfile)
        return subprocess_call_wrapper(
            ['jdb', '-classpath', classpath, classname])


class Fortran(CompiledLanguages):
    """Fortran"""
    name = 'fortran'
    extensions = ['f', 'for', 'f90', 'f95']
    compiler = 'gfortran'
    compiler_options = ['--free-form']

    information = dedent('''
- Wikipedia page : http://en.wikipedia.org/wiki/Fortran
- Official site : 
- Documentation : http://www.fortran90.org/
- Subreddit : http://www.reddit.com/r/fortran/ 
- Tools online :
- RosettaCode : http://rosettacode.org/wiki/Category:Fortran
    ''')

    @classmethod
    def get_file_content(cls, _):
        """Returns the content to be put in the file."""
        return dedent('''
            program helloworld
            print *, "Hello, world!"
            end program helloworld
            ''')

    @classmethod
    def debug(cls, args):
        """Launches the debugger"""
        output = cls.get_output_filename(cls.get_actual_filename_to_use(args))
        return subprocess_call_wrapper(['gdb', output])

    @classmethod
    def metrics(cls, args):
        """Gets metrics for code"""
        filename = cls.get_actual_filename_to_use(args)
        return subprocess_call_wrapper(['fortran_count', filename])


class Haskell(CompiledLanguages):
    """Haskell"""
    name = 'haskell'
    extensions = ['hs', 'lhs']
    compiler = 'ghc'
    information = dedent('''
- Wikipedia page : http://en.wikipedia.org/wiki/Haskell_%28programming_language%29
- Official site : http://www.haskell.org/
- Documentation : http://www.haskell.org/ghc/docs/7.6-latest/html/libraries/index.html
- Subreddit : http://www.reddit.com/r/haskell/
- Tools online :
    * Try Haskell : http://tryhaskell.org/
- RosettaCode : http://rosettacode.org/wiki/Category:Haskell
''')

    @classmethod
    def get_file_content(cls, _):
        """Returns the content to be put in the file."""
        return dedent('''
            main = putStrLn "Hello, world!"
            ''')

    @classmethod
    def debug(cls, args):
        """Launches the debugger"""
        # Another option is to use GHCi
        output = cls.get_output_filename(cls.get_actual_filename_to_use(args))
        return subprocess_call_wrapper(['gdb', output])

    @classmethod
    def metrics(cls, args):
        """Gets metrics for code"""
        filename = cls.get_actual_filename_to_use(args)
        return subprocess_call_wrapper(['haskell_count', filename])


class MarkupLanguage(Language):
    """A generic class for markup languages"""


class HTML(MarkupLanguage):
    """HTML"""
    name = 'html'
    extensions = ['html']
    information = dedent('''
- Wikipedia page : http://en.wikipedia.org/wiki/HTML
- Official site : http://www.w3.org/
- Subreddit : http://www.reddit.com/r/html
- Tools online :
    * W3C Validator : http://validator.w3.org/
''')

    @classmethod
    def pretty(cls, args):
        """Makes the code prettier"""
        filename = cls.get_actual_filename_to_use(args)
{'fraction_non_alphanumeric': 0.12202682736043109, 'fraction_numerical': 0.003824202566851117, 'mean_word_length': 2.6315623356128355, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 28, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1587167', 'n_tokens_mistral': 10298, 'n_tokens_neox': 10085, 'n_words': 2944}