├── tests
├── __init__.py
├── requirements.txt
├── test_js.py
└── data
│ └── js-react-native-app
│ └── react-native-app.js
├── requirements.txt
├── ramile
├── data
│ └── template.docx
├── __init__.py
├── processors
│ ├── double_slash_comment_filter.py
│ ├── comment_block_filter.py
│ ├── blank_line_filter.py
│ ├── go_processor.py
│ ├── oc_processor.py
│ ├── java_processor.py
│ ├── swift_processor.py
│ ├── js_processor.py
│ ├── css_processor.py
│ ├── php_processor.py
│ ├── html_processor.py
│ ├── c_style_comment_block_filter.py
│ ├── html_comment_block_filter.py
│ └── __init__.py
├── file_info.py
├── project_processor.py
├── project_info.py
└── project.py
├── template.ramileconfig.json
├── .vscode
└── settings.json
├── ramile-cli.py
├── LICENSE
├── .gitignore
├── 著作权法.md
└── README.md
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/requirements.txt:
--------------------------------------------------------------------------------
1 | pytest
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | fire
2 | python-docx
--------------------------------------------------------------------------------
/ramile/data/template.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luxel/ramile/HEAD/ramile/data/template.docx
--------------------------------------------------------------------------------
/template.ramileconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "source_root": "",
3 | "ignore": [],
4 | "filters": [],
5 | "lines_to_extract": 3000
6 | }
--------------------------------------------------------------------------------
/ramile/__init__.py:
--------------------------------------------------------------------------------
1 | """ Ramile project
2 | """
3 | from ramile.project import Project
4 | from ramile.project_info import ProjectInfo
5 | from ramile.file_info import FileInfo
6 |
7 | __all__ = ['Project', 'ProjectInfo', 'FileInfo']
8 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[python]": {
3 | "editor.insertSpaces": true,
4 | "editor.tabSize": 4
5 | },
6 | "editor.formatOnSave": true,
7 | "files.exclude": {
8 | "__pycache__": true,
9 | "scripts/__pycache__": true
10 | }
11 | }
--------------------------------------------------------------------------------
/ramile/processors/double_slash_comment_filter.py:
--------------------------------------------------------------------------------
1 | from ramile.processors import LineFilterBase
2 |
3 |
4 | class DoubleSlashCommentFilter(LineFilterBase):
5 | """ Filters out single line comments which start with '//'
6 | """
7 |
8 | def filter(self, file, line):
9 | if line.startswith('//'):
10 | file.found_comment_line()
11 | return line, True
12 | return line, False
13 |
--------------------------------------------------------------------------------
/ramile/processors/comment_block_filter.py:
--------------------------------------------------------------------------------
1 | from ramile.processors import LineFilterBase
2 |
3 |
4 | class CommentBlockFilter(LineFilterBase):
5 | """ Filters out everything - when the file is currently in a comment block.
6 | """
7 |
8 | def filter(self, file, line):
9 | if file.is_in_comment_block:
10 | file.found_comment_line()
11 | return line, True
12 | return line, False
13 |
--------------------------------------------------------------------------------
/ramile/processors/blank_line_filter.py:
--------------------------------------------------------------------------------
1 | from ramile.processors import LineFilterBase
2 |
3 |
4 | class BlankLineFilter(LineFilterBase):
5 | """ Filters out blank lines - lines with only spaces, tabs and line ends. It also strips the line for later processing.
6 | """
7 |
8 | def filter(self, file, line):
9 | stripped_line = line.strip(' \t\n')
10 | if stripped_line == '':
11 | file.found_blank_line()
12 | return stripped_line, True
13 | return stripped_line, False
14 |
--------------------------------------------------------------------------------
/ramile/processors/go_processor.py:
--------------------------------------------------------------------------------
1 | from ramile.processors import FileProcessorBase
2 | from ramile.processors import BlankLineFilter
3 | from ramile.processors.c_style_comment_block_filter import CStyleCommentBlockFilter
4 | from ramile.processors.double_slash_comment_filter import DoubleSlashCommentFilter
5 |
6 |
7 | class GoProcessor(FileProcessorBase):
8 | expected_extensions = ['.go']
9 |
10 | def __init__(self):
11 | self.filters.append(BlankLineFilter())
12 | self.filters.append(CStyleCommentBlockFilter())
13 | self.filters.append(DoubleSlashCommentFilter())
14 | return
15 |
--------------------------------------------------------------------------------
/ramile/processors/oc_processor.py:
--------------------------------------------------------------------------------
1 | from ramile.processors import FileProcessorBase
2 | from ramile.processors import BlankLineFilter
3 | from ramile.processors.c_style_comment_block_filter import CStyleCommentBlockFilter
4 | from ramile.processors.double_slash_comment_filter import DoubleSlashCommentFilter
5 |
6 |
7 | class OCProcessor(FileProcessorBase):
8 | expected_extensions = ['.m']
9 |
10 | def __init__(self):
11 | self.filters.append(BlankLineFilter())
12 | self.filters.append(CStyleCommentBlockFilter())
13 | self.filters.append(DoubleSlashCommentFilter())
14 | return
15 |
--------------------------------------------------------------------------------
/ramile/processors/java_processor.py:
--------------------------------------------------------------------------------
1 | from ramile.processors import FileProcessorBase
2 | from ramile.processors import BlankLineFilter
3 | from ramile.processors.c_style_comment_block_filter import CStyleCommentBlockFilter
4 | from ramile.processors.double_slash_comment_filter import DoubleSlashCommentFilter
5 |
6 |
7 | class JavaProcessor(FileProcessorBase):
8 | expected_extensions = ['.java']
9 |
10 | def __init__(self):
11 | self.filters.append(BlankLineFilter())
12 | self.filters.append(CStyleCommentBlockFilter())
13 | self.filters.append(DoubleSlashCommentFilter())
14 | return
15 |
--------------------------------------------------------------------------------
/ramile/processors/swift_processor.py:
--------------------------------------------------------------------------------
1 | from ramile.processors import FileProcessorBase
2 | from ramile.processors import BlankLineFilter
3 | from ramile.processors.c_style_comment_block_filter import CStyleCommentBlockFilter
4 | from ramile.processors.double_slash_comment_filter import DoubleSlashCommentFilter
5 |
6 |
7 | class SwiftProcessor(FileProcessorBase):
8 | expected_extensions = ['.swift']
9 |
10 | def __init__(self):
11 | self.filters.append(BlankLineFilter())
12 | self.filters.append(CStyleCommentBlockFilter())
13 | self.filters.append(DoubleSlashCommentFilter())
14 | return
15 |
--------------------------------------------------------------------------------
/ramile/processors/js_processor.py:
--------------------------------------------------------------------------------
1 | from ramile.processors import FileProcessorBase
2 | from ramile.processors import BlankLineFilter
3 | from ramile.processors.c_style_comment_block_filter import CStyleCommentBlockFilter
4 | from ramile.processors.double_slash_comment_filter import DoubleSlashCommentFilter
5 |
6 |
7 | class JsProcessor(FileProcessorBase):
8 | expected_extensions = ['.js', '.jsx', '.vue', '.wpy']
9 |
10 | def __init__(self):
11 | self.filters.append(BlankLineFilter())
12 | self.filters.append(CStyleCommentBlockFilter())
13 | self.filters.append(DoubleSlashCommentFilter())
14 | return
15 |
--------------------------------------------------------------------------------
/ramile/processors/css_processor.py:
--------------------------------------------------------------------------------
1 | from ramile.processors import FileProcessorBase
2 | from ramile.processors import BlankLineFilter
3 | from ramile.processors.c_style_comment_block_filter import CStyleCommentBlockFilter
4 | from ramile.processors.double_slash_comment_filter import DoubleSlashCommentFilter
5 | from ramile.processors.html_comment_block_filter import HtmlCommentBlockFilter
6 |
7 |
8 | class CssProcessor(FileProcessorBase):
9 | expected_extensions = ['.css', '.less', '.sass']
10 |
11 | def __init__(self):
12 | self.filters.append(BlankLineFilter())
13 | self.filters.append(CStyleCommentBlockFilter())
14 | self.filters.append(DoubleSlashCommentFilter())
15 | return
16 |
--------------------------------------------------------------------------------
/ramile/processors/php_processor.py:
--------------------------------------------------------------------------------
1 | from ramile.processors import FileProcessorBase
2 | from ramile.processors import BlankLineFilter
3 | from ramile.processors.c_style_comment_block_filter import CStyleCommentBlockFilter
4 | from ramile.processors.double_slash_comment_filter import DoubleSlashCommentFilter
5 | from ramile.processors.html_comment_block_filter import HtmlCommentBlockFilter
6 |
7 |
8 | class PhpProcessor(FileProcessorBase):
9 | expected_extensions = ['.php']
10 |
11 | def __init__(self):
12 | self.filters.append(BlankLineFilter())
13 | self.filters.append(CStyleCommentBlockFilter())
14 | self.filters.append(DoubleSlashCommentFilter())
15 | self.filters.append(HtmlCommentBlockFilter())
16 | return
17 |
--------------------------------------------------------------------------------
/ramile/processors/html_processor.py:
--------------------------------------------------------------------------------
1 | from ramile.processors import FileProcessorBase
2 | from ramile.processors import BlankLineFilter
3 | from ramile.processors.c_style_comment_block_filter import CStyleCommentBlockFilter
4 | from ramile.processors.double_slash_comment_filter import DoubleSlashCommentFilter
5 | from ramile.processors.html_comment_block_filter import HtmlCommentBlockFilter
6 |
7 |
8 | class HtmlProcessor(FileProcessorBase):
9 | expected_extensions = ['.html', '.htm']
10 |
11 | def __init__(self):
12 | self.filters.append(BlankLineFilter())
13 | self.filters.append(CStyleCommentBlockFilter())
14 | self.filters.append(DoubleSlashCommentFilter())
15 | self.filters.append(HtmlCommentBlockFilter())
16 | return
17 |
--------------------------------------------------------------------------------
/ramile-cli.py:
--------------------------------------------------------------------------------
1 | #! python
2 | import fire
3 |
4 | from ramile.project import Project
5 | from ramile.processors import FileProcessor
6 |
7 |
8 | class ramile_cli(object):
9 |
10 | def extract(self, project_root, lines_to_extract=3000):
11 | project = Project(project_root, lines_to_extract)
12 | project.run()
13 | return
14 |
15 | def whatcanido(self):
16 | processor = FileProcessor()
17 | print("I can extract source code from files with these extensions: %s" %
18 | processor.print_processors())
19 | return
20 |
21 | def test(self):
22 | print('(%s)' % ' abced '.strip(' \t'))
23 |
24 |
25 | def main():
26 | fire.Fire(ramile_cli(), name='ramile')
27 |
28 |
29 | if __name__ == '__main__':
30 | main()
31 |
--------------------------------------------------------------------------------
/ramile/processors/c_style_comment_block_filter.py:
--------------------------------------------------------------------------------
1 | from ramile.processors import LineFilterBase
2 |
3 |
4 | class CStyleCommentBlockFilter(LineFilterBase):
5 | """ Filters out C-style comment blocks with '/*' and '*/'
6 | """
7 |
8 | def filter(self, file, line):
9 | if file.is_in_comment_block:
10 | file.found_comment_line()
11 | if line.endswith('*/'):
12 | file.mark_comment_block_end()
13 | return line, True
14 | else:
15 | if line.startswith('/*'):
16 | file.found_comment_line()
17 | file.mark_comment_block_start()
18 | if line.endswith('*/'):
19 | file.mark_comment_block_end()
20 | return line, True
21 | return line, False
22 |
--------------------------------------------------------------------------------
/ramile/processors/html_comment_block_filter.py:
--------------------------------------------------------------------------------
1 | from ramile.processors import LineFilterBase
2 |
3 |
4 | class HtmlCommentBlockFilter(LineFilterBase):
5 | """ Filters out html comment blocks with ''
6 | """
7 |
8 | def filter(self, file, line):
9 | if file.is_in_comment_block:
10 | file.found_comment_line()
11 | if line.endswith('-->'):
12 | file.mark_comment_block_end()
13 | return line, True
14 | else:
15 | if line.startswith(''):
19 | file.mark_comment_block_end()
20 | return line, True
21 | return line, False
22 |
--------------------------------------------------------------------------------
/tests/test_js.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from ramile import *
3 |
4 |
5 | def test_react_native_app():
6 | """ Test with react-native app project. cloc results:
7 | -------------------------------------------------------------------------------
8 | Language files blank comment code
9 | -------------------------------------------------------------------------------
10 | JavaScript 1 6 6 21
11 | -------------------------------------------------------------------------------
12 | """
13 | project = Project('data/js-react-native-app',
14 | 3000, 'test-output.txt')
15 | project.run()
16 | assert project.info.lines_extracted == 21
17 | assert project.info.lines_skipped_blank == 6
18 | assert project.info.lines_skipped_comments == 6
19 | return
20 |
--------------------------------------------------------------------------------
/tests/data/js-react-native-app/react-native-app.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This is an app.js created by create-react-native-app
3 | */
4 | import React from 'react';
5 | import { StyleSheet, Text, View } from 'react-native';
6 |
7 | export default class App extends React.Component {
8 | render() {
9 | return (
10 |
11 | Open up App.js to start working on your app!
12 | Changes you make will automatically reload.
13 | Shake your phone to open the developer menu.
14 |
15 | );
16 | }
17 | }
18 |
19 | const styles = StyleSheet.create({
20 | container: {
21 | flex: 1,
22 | backgroundColor: '#fff',
23 | alignItems: 'center',
24 | justifyContent: 'center',
25 | },
26 | });
27 |
28 | // This comment is added intentionally for test
29 | /* Test comment block in single line */
30 | // Below are some blanks lines added for testing
31 |
32 |
33 |
--------------------------------------------------------------------------------
/ramile/file_info.py:
--------------------------------------------------------------------------------
1 | class FileInfo(object):
2 | file_name = ''
3 | file_extension = ''
4 | file_path = ''
5 | is_in_comment_block = False
6 | blank_lines = 0
7 | comment_lines = 0
8 | extracted_lines = 0
9 |
10 | def __init__(self, file_path, file_name, file_extension):
11 | self.file_path = file_path
12 | self.file_extension = file_extension
13 | self.file_name = file_name
14 | return
15 |
16 | def has_extracted_lines(self):
17 | return self.extracted_lines > 0
18 |
19 | def extracted_line(self):
20 | self.extracted_lines += 1
21 | return
22 |
23 | def found_blank_line(self):
24 | self.blank_lines += 1
25 | return
26 |
27 | def found_comment_line(self):
28 | self.comment_lines += 1
29 | return
30 |
31 | def mark_comment_block_start(self):
32 | self.is_in_comment_block = True
33 | return
34 |
35 | def mark_comment_block_end(self):
36 | self.is_in_comment_block = False
37 | return
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Luxel Tao
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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 |
57 | # Flask stuff:
58 | instance/
59 | .webassets-cache
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # Jupyter Notebook
71 | .ipynb_checkpoints
72 |
73 | # pyenv
74 | .python-version
75 |
76 | # celery beat schedule file
77 | celerybeat-schedule
78 |
79 | # SageMath parsed files
80 | *.sage.py
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | .venv
87 | venv/
88 | ENV/
89 |
90 | # Spyder project settings
91 | .spyderproject
92 | .spyproject
93 |
94 | # Rope project settings
95 | .ropeproject
96 |
97 | # mkdocs documentation
98 | /site
99 |
100 | # mypy
101 | .mypy_cache/
102 |
103 | # Ramile project ignores
104 | test-output*
105 | .DS_Store
106 |
--------------------------------------------------------------------------------
/ramile/project_processor.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from ramile.file_info import FileInfo
4 | from ramile.processors import FileProcessor
5 |
6 |
7 | class ProjectProcessor(object):
8 |
9 | def __init__(self, project):
10 | self.project = project
11 | self.files = []
12 | self.file_processor = FileProcessor()
13 | return
14 |
15 | def process(self):
16 | self.find_files()
17 | self.sort_files()
18 | return self.files
19 |
20 | def find_files(self):
21 | for root, dirs, files in os.walk(self.project.source_root):
22 | if self.is_ignored(root):
23 | continue
24 | for file_name in files:
25 | file_path = os.path.join(root, file_name)
26 | if self.is_ignored(file_path):
27 | continue
28 | name, extension = os.path.splitext(file_name)
29 | if self.is_interested_file(name, extension):
30 | info = FileInfo(file_path, file_name, extension)
31 | self.files.append(info)
32 | return
33 |
34 | def walk(self):
35 | return
36 |
37 | def is_ignored(self, path):
38 | """ Checks whether the specified path is ignored.
39 | """
40 | for ignore in self.project.ignore:
41 | if path.startswith(ignore):
42 | return True
43 | return False
44 |
45 | def is_interested_file(self, filename, extension):
46 | is_interested = True
47 | if len(self.project.filters) > 0:
48 | is_interested = False
49 | for filter in self.project.filters:
50 | if extension == filter:
51 | is_interested = True
52 | break
53 |
54 | if is_interested:
55 | return self.file_processor.has_interest(extension)
56 | else:
57 | return False
58 |
59 | def sort_files(self):
60 | return
61 |
--------------------------------------------------------------------------------
/ramile/project_info.py:
--------------------------------------------------------------------------------
1 | import os.path
2 | import json
3 | import io
4 |
5 |
6 | class ProjectInfo(object):
7 | """Represents all the information for a project.
8 |
9 | Attributes:
10 | source_root Root directory of the source code folder for parsing.
11 | lines_to_extract How many lines to extract, default to 3000
12 | """
13 | lines_to_extract = 3000
14 | project_root = ''
15 | source_root = ''
16 | lines_extracted = 0
17 | lines_skipped_comments = 0
18 | lines_skipped_blank = 0
19 | ignore = []
20 | filters = []
21 |
22 | def __init__(self, project_root, lines_to_extract):
23 | self.project_root = project_root
24 | self.lines_to_extract = float(lines_to_extract)
25 | self.parse_config()
26 | return
27 |
28 | def parse_config(self):
29 | """ Try to parse .ramileconfig.json file from the project root.
30 | """
31 | config_file = os.path.join(self.project_root, '.ramileconfig.json')
32 | if os.path.exists(config_file):
33 | with io.open(config_file, 'r') as file:
34 | config_data = json.load(file)
35 | print(config_data)
36 | self.__set_config_root(config_data)
37 | self.__set_config_ignore(config_data)
38 | self.__set_config_filters(config_data)
39 | self.__set_config_lines_to_extract(config_data)
40 | if self.source_root == '':
41 | self.source_root = self.project_root
42 | return
43 |
44 | def __set_config_filters(self, config_data):
45 | if 'filters' in config_data:
46 | self.filters = config_data['filters']
47 |
48 | def __set_config_root(self, config_data):
49 | if 'source_root' in config_data:
50 | self.source_root = os.path.join(
51 | self.project_root, config_data['source_root'])
52 |
53 | def __set_config_ignore(self, config_data):
54 | if 'ignore' in config_data:
55 | ignores = config_data['ignore']
56 | for ignore in ignores:
57 | self.ignore.append(os.path.join(self.source_root, ignore))
58 | print('Paths to be ignored: ', self.ignore)
59 |
60 | def __set_config_lines_to_extract(self, config_data):
61 | if 'lines_to_extract' in config_data:
62 | self.lines_to_extract = float(config_data['lines_to_extract'])
63 |
64 | def has_extracted_enough_lines(self):
65 | return self.lines_extracted >= self.lines_to_extract
66 |
67 | def get_output_file_path(self, output_filename):
68 | return os.path.join(self.project_root, output_filename)
69 |
--------------------------------------------------------------------------------
/ramile/project.py:
--------------------------------------------------------------------------------
1 | from docx import Document
2 | from ramile.project_info import ProjectInfo
3 | from ramile.project_processor import ProjectProcessor
4 | from ramile.processors import FileProcessor
5 | import os
6 |
7 |
8 | class Project(object):
9 | info = None
10 | output = True
11 | files = []
12 |
13 | def __init__(self, project_root, lines_to_extract=3000, output_file='extracted_code.docx', output=True):
14 | self.info = ProjectInfo(project_root, lines_to_extract)
15 | self.output = output
16 | if output:
17 | self.output_path = self.info.get_output_file_path(output_file)
18 | # self.output_file = open(
19 | # self.info.get_output_file_path(output_file), 'w+')
20 | self.output_file = Document(os.path.join(
21 | os.path.dirname(__file__), 'data/template.docx'))
22 | self.paragraph = None
23 | return
24 |
25 | def run(self, output=True, echo=True):
26 | if echo:
27 | print("I'm going to extract %s lines from %s." %
28 | (self.info.lines_to_extract, self.info.project_root))
29 | self.info.lines_extracted = 0
30 | project_processor = ProjectProcessor(self.info)
31 | file_processor = FileProcessor()
32 | # 1. Process and collect the files
33 | self.files = project_processor.process()
34 | # 2. Process each file
35 | for file in self.files:
36 | for output in file_processor.process(file):
37 | self.export(output)
38 | file.extracted_line()
39 | if self.info.has_extracted_enough_lines():
40 | break
41 | # collect file summary
42 | self.info.lines_skipped_blank += file.blank_lines
43 | self.info.lines_skipped_comments += file.comment_lines
44 | if self.info.has_extracted_enough_lines():
45 | break
46 | # self.output_file.close()
47 | print('current file = ', __file__)
48 | self.output_file.save(self.output_path)
49 | if echo:
50 | self.print_summary()
51 |
52 | if not self.info.has_extracted_enough_lines():
53 | print("Warning!! Not enough source code to extract %s lines!" %
54 | self.info.lines_to_extract)
55 | return
56 |
57 | def print_summary(self):
58 | print("The extraction is done. Here's the summary:")
59 | print("Files that contributed to the output:")
60 | for file in self.files:
61 | if file.has_extracted_lines():
62 | print("%s : %s lines" % (file.file_path, file.extracted_lines))
63 | print("Code was extracted in: %s" % self.output_path)
64 | print("Total extracted: %s lines" % self.info.lines_extracted)
65 | print("Total skipped comments: %s lines" %
66 | self.info.lines_skipped_comments)
67 | print("Total skipped blank lines: %s lines" %
68 | self.info.lines_skipped_blank)
69 |
70 | def export(self, line):
71 | if self.output:
72 | # self.output_file.write(line)
73 | # if not line.endswith('\n'):
74 | # self.output_file.write('\n')
75 | if self.paragraph is None:
76 | self.paragraph = self.output_file.paragraphs[0]
77 | self.paragraph.add_run(line)
78 | self.info.lines_extracted += 1
79 | return
80 |
--------------------------------------------------------------------------------
/著作权法.md:
--------------------------------------------------------------------------------
1 | 计算机软件著作权登记办法 2002 年 2 月 20 日 国家版权局令第 1 号
2 |
3 | 第一章 总则
4 |
5 | 第一条 为贯彻《计算机软件保护条例》(以下简称《条例》)制定本办法。
6 |
7 | 第二条 为促进我国软件产业发展,增强我国信息产业的创新能力和竞争能力,国家著作权行政管理部门鼓励软件登记,并对登记的软件予以重点保护。
8 |
9 | 第三条 本办法适用于软件著作权登记、软件著作权专有许可合同和转让合同登记。
10 |
11 | 第四条 软件著作权登记申请人应当是该软件的著作权人以及通过继承、受让或者承受软件著作权的自然人、法人或者其他组织。
12 |
13 | 软件著作权合同登记的申请人,应当是软件著作权专有许可合同或者转让合同的当事人。
14 |
15 | 第五条 申请人或者申请人之一为外国人、无国籍人的,适用本办法。
16 |
17 | 第六条 国家版权局主管全国软件著作权登记管理工作。
18 |
19 | 国家版权局认定中国版权保护中心为软件登记机构。
20 |
21 | 经国家版权局批准,中国版权保护中心可以在地方设立软件登记办事机构。
22 |
23 | 第二章 登记申请
24 |
25 | 第七条 申请登记的软件应是独立开发的,或者经原著作权人许可对原有软件修改后形成的在功能或者性能方面有重要改进的软件。
26 |
27 | 第八条 合作开发的软件进行著作权登记的,可以由全体著作权人协商确定一名著作权人作为代表办理。著作权人协商不一致的,任何著作权人均可在不损害其他著作权人利益的前提下申请登记,但应当注明其他著作权人。
28 |
29 | 第九条 申请软件著作权登记的,应当向中国版权保护中心提交以下材料:
30 |
31 | (一)按要求填写的软件著作权登记申请表;
32 |
33 | (二)软件的鉴别材料;
34 |
35 | (三)相关的证明文件。
36 |
37 | ### 第十条 软件的鉴别材料包括程序和文档的鉴别材料。
38 |
39 | 程序和文档的鉴别材料应当由源程序和任何一种文档前、后各连续 30 页组成。整个程序和文档不到 60 页的,应当提交整个源程序和文档。除特定情况外,程序每页不少于 50 行,文档每页不少于 30 行。
40 |
41 | 第十一条 申请软件著作权登记的,应当提交以下主要证明文件:
42 |
43 | (一)自然人、法人或者其他组织的身份证明;
44 |
45 | (二)有著作权归属书面合同或者项目任务书的,应当提交合同或者项目任务书;
46 |
47 | (三)经原软件著作权人许可,在原有软件上开发的软件,应当提交原著作权人的许可证明;
48 |
49 | (四)权利继承人、受让人或者承受人,提交权利继承、受让或者承受的证明。
50 |
51 | 第十二条 申请软件著作权登记的,可以选择以下方式之一对鉴别材料作例外交存:
52 |
53 | (一)源程序的前、后各连续的 30 页,其中的机密部分用黑色宽斜线覆盖,但覆盖部分不得超过交存源程序的 50%;
54 |
55 | (二)源程序连续的前 10 页,加上源程序的任何部分的连续的 50 页;
56 |
57 | (三)目标程序的前、后各连续的 30 页,加上源程序的任何部分的连续的 20 页。
58 |
59 | 文档作例外交存的,参照前款规定处理。
60 |
61 | 第十三条 软件著作权登记时,申请人可以申请将源程序、文档或者样品进行封存。除申请人或者司法机关外,任何人不得启封。
62 |
63 | 第十四条 软件著作权转让合同或者专有许可合同当事人可以向中国版权保护中心申请合同登记。申请合同登记时,应当提交以下材料:
64 |
65 | (一)按要求填写的合同登记表;
66 |
67 | (二)合同复印件;
68 |
69 | (三)申请人身份证明。
70 |
71 | 第十五条 申请人在登记申请批准之前,可以随时请求撤回申请。
72 |
73 | 第十六条 软件著作权登记人或者合同登记人可以对已经登记的事项作变更或者补充。申请登记变更或者补充时,申请人应当提交以下材料:
74 |
75 | (一)按照要求填写的变更或者补充申请表;
76 |
77 | (二)登记证书或者证明的复印件;
78 |
79 | (三)有关变更或者补充的材料。
80 |
81 | 第十七条 登记申请应当使用中国版权保护中心制定的统一表格,并由申请人盖章(签名)。
82 |
83 | 申请表格应当使用中文填写。提交的各种证件和证明文件是外文的,应当附中文译本。
84 |
85 | 申请登记的文件应当使用国际标准 A4 型 297mm X 210mm(长 X 宽)纸张。
86 |
87 | 第十八条 申请文件可以直接递交或者挂号邮寄。申请人提交有关申请文件时,应当注明申请人、软件的名称,有受理号或登记号的,应当注明受理号或登记号。
88 |
89 | 第三章 审查和批准
90 |
91 | 第十九条 对于本办法第九条和第十四条所指的申请,以收到符合本办法第二章规定的材料之日为受理日,并书面通知申请人。
92 |
93 | 第二十条 中国版权保护中心应当自受理日起 60 日内审查完成所受理的申请,申请符合《条例》和本办法规定的,予以登记,发给相应的登记证书,并予以公告。
94 |
95 | 第二十一条 有下列情况之一的,不予登记并书面通知申请人:
96 |
97 | (一)表格内容填写不完整、不规范,且未在指定期限内补正的;
98 |
99 | (二)提交的鉴别材料不是《条例》规定的软件程序和文档的;
100 |
101 | (三)申请文件中出现的软件名称、权利人署名不一致,且未提交证明文件的;
102 |
103 | (四)申请登记的软件存在权属争议的。
104 |
105 | 第二十二条 中国版权保护中心要求申请人补正其他登记材料的,申请人应当在 30 日内补正,逾期未补正的,视为撤回申请。
106 |
107 | 第二十三条 国家版权局根据下列情况之一,可以撤销登记:
108 |
109 | (一)最终的司法判决;
110 |
111 | (二)著作权行政管理部门作出的行政处罚决定。
112 |
113 | 第二十四条 中国版权保护中心可以根据申请人的申请,撤销登记。
114 |
115 | 第二十五条 登记证书遗失或损坏的,可申请补发或换发。
116 |
117 | 第四章 软件登记公告
118 |
119 | 第二十六条 除本办法另有规定外,任何人均可查阅软件登记公告以及可公开的有关登记文件。
120 |
121 | 第二十七条 软件登记公告的内容如下:
122 |
123 | (一)软件著作权的登记;
124 |
125 | (二)软件著作权合同登记事项;
126 |
127 | (三)软件登记的撤销;
128 |
129 | (四)其他事项。
130 |
131 | 第五章 费用
132 |
133 | 第二十八条 申请软件登记或者办理其他事项,应当交纳下列费用:
134 |
135 | (一)软件著作权登记费;
136 |
137 | (二)软件著作权合同登记费;
138 |
139 | (三)变更或补充登记费;
140 |
141 | (四)登记证书费;
142 |
143 | (五)封存保管费;
144 |
145 | (六)例外交存费;
146 |
147 | (七)查询费;
148 |
149 | (八)撤销登记申请费;
150 |
151 | (九)其他需交纳的费用。
152 |
153 | 具体收费标准由国家版权局会同国务院价格主管部门规定并公布。
154 |
155 | 第二十九条 申请人自动撤回申请或者登记机关不予登记的,所交费用不予退回。
156 |
157 | 第三十条 本办法第二十八条规定的各种费用,可以通过邮局或银行汇付,也可以直接向中国版权保护中心交纳。
158 |
159 | 第六章 附则
160 |
161 | 第三十一条 本办法规定的、中国版权保护中心指定的各种期限,第一日不计算在内。期限以年或者月计算的,以最后一个月的相应日为届满日;该月无相应日的,以该月的最后一日为届满日。届满日是法定节假日的,以节假日后的第一个工作日为届满日。
162 |
163 | 第三十二条 申请人向中国版权保护中心邮寄的各种文件,以寄出的邮戳日为递交日。信封上寄出的邮戳日不清晰的,除申请人提出证明外,以收到日为递交日。中国版权保护中心邮寄的各种文件,送达地是省会、自治区首府及直辖市的,自文件发出之日满十五日,其他地区满二十一日,推定为收件人收到文件之日。
164 |
165 | 第三十三条 申请人因不可抗力或其他正当理由,延误了本办法规定或者中国版权保护中心指定的期限,在障碍消除后三十日内,可以请求顺延期限。
166 |
167 | 第三十四条 本办法由国家版权局负责解释和补充修订。
168 |
169 | 第三十五条 本办法自发布之日起实施。
170 |
--------------------------------------------------------------------------------
/ramile/processors/__init__.py:
--------------------------------------------------------------------------------
1 | import io
2 |
3 |
4 | class FileProcessorBase(object):
5 | """ Base class for file processors. The processor for each lanuage should inherit from this class.
6 | """
7 | expected_extensions = []
8 | filters = []
9 |
10 | def __init__(self):
11 | # by default, processors of all languages will always starts with a blank line filter
12 | self.filters.append(BlankLineFilter())
13 | return
14 |
15 | def is_interested_in(self, extension):
16 | """ Checks whether the filter is interested in the provided file extension.
17 | """
18 | for expected in self.expected_extensions:
19 | if expected == extension:
20 | return True
21 | return False
22 |
23 | def process(self, file):
24 | """ Processes a file and extracts lines out of it.
25 | """
26 | with io.open(file.file_path, 'r', encoding='utf-8') as open_file:
27 | last_line = None
28 | for original_line in open_file:
29 | if self.process_line(file, original_line):
30 | yield original_line
31 | last_line = original_line
32 | return
33 |
34 | def process_line(self, file, line):
35 | """ Processes a line, returns true if the line should be extracted.
36 | """
37 | for filter in self.filters:
38 | line, dropped = filter.filter(file, line)
39 | if dropped:
40 | return False
41 | return True
42 |
43 | def add_filter(self, filter):
44 | self.filters.append(filter)
45 | return
46 |
47 |
48 | class LineFilterBase(object):
49 | """ A filter will process each line, and determine whether each line should be dropped. A filter can also perform any neccessary process on the line and replace the original line.
50 | """
51 |
52 | def filter(self, line):
53 | """ Filters a line of code, outputs the filtered content, and a flag whether the line should be dropped.
54 | """
55 | return line, False
56 |
57 |
58 | class FileProcessor(object):
59 |
60 | def __init__(self):
61 | self.processors = {}
62 | self.__build_processors()
63 | return
64 |
65 | def print_processors(self):
66 | return list(self.processors.keys())
67 |
68 | def has_interest(self, extension):
69 | return extension in self.processors
70 |
71 | def process(self, file):
72 | processor = self.__get_cached_processor(file.file_extension)
73 | for output in processor.process(file):
74 | yield output
75 |
76 | def __build_processors(self):
77 | """ Register and cache all supported file processors.
78 | """
79 | self.__cache_processor(JsProcessor())
80 | self.__cache_processor(JavaProcessor())
81 | self.__cache_processor(PhpProcessor())
82 | self.__cache_processor(HtmlProcessor())
83 | self.__cache_processor(CssProcessor())
84 | self.__cache_processor(SwiftProcessor())
85 | self.__cache_processor(OCProcessor())
86 | self.__cache_processor(GoProcessor())
87 | return
88 |
89 | def __get_cached_processor(self, extension):
90 | return self.processors[extension]
91 |
92 | def __cache_processor(self, processor):
93 | if len(processor.expected_extensions) > 0:
94 | for extension in processor.expected_extensions:
95 | self.processors[extension] = processor
96 | return
97 |
98 |
99 | from ramile.processors.blank_line_filter import BlankLineFilter
100 | from ramile.processors.comment_block_filter import CommentBlockFilter
101 | from ramile.processors.js_processor import JsProcessor
102 | from ramile.processors.java_processor import JavaProcessor
103 | from ramile.processors.php_processor import PhpProcessor
104 | from ramile.processors.html_processor import HtmlProcessor
105 | from ramile.processors.css_processor import CssProcessor
106 | from ramile.processors.swift_processor import SwiftProcessor
107 | from ramile.processors.oc_processor import OCProcessor
108 | from ramile.processors.go_processor import GoProcessor
109 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ramile
2 |
3 | _Ramile a handy tool used to automatically extract 3000 lines of source codes from given project/folder, as a requirement of China Software Copyright application process. The goal of Ramile is to save 0.5~1 hour of your time spent on preparing the 60 pages of source code for each Software Copyright submission._
4 | Currently Ramile has below features:
5 |
6 | - Automatically extracting the source code and generating a docx file containing 3000 lines. (You have to manually remove the last few pages of the docx to make it exactly 60 pages, though)
7 | - Supporting most of the commmon front-end projects: android/ios/web/Wechat mini program, etc
8 | - Configurable. Just place a `.ramileconfig.json` under the project root folder. (See "Config" section for details)
9 |
10 | Tested under python 3.6.1.
11 |
12 | ## Installation
13 |
14 | Right now we can only run Ramile from source code. In the future it may be uploaded to pypi.
15 |
16 | To run Ramile source code, clone the repository and install dependencies: `pip install -r requirements.txt`. Or if in China, mirros could be used `pip install -i https://pypi.mirrors.ustc.edu.cn/simple/ -r requirements.txt`
17 |
18 | ## Basic Usage
19 |
20 | Running from source code:
21 |
22 | ```
23 | python ramile-cli.py extract
24 | ```
25 |
26 | When the extraction is completed, a file named `extracted_code.docx` will be generated under your project root directory, with 3000 lines of code. You just have to open it and remove unnecessary pages to make the document exact 60 pages.
27 |
28 | If you want to strictly meet the [regulation](./著作权法.md#第十条-软件的鉴别材料包括程序和文档的鉴别材料), you can extract all the lines by append `Inf` to the command line:
29 |
30 | ```
31 | python ramile-cli.py extract Inf
32 | ```
33 |
34 | And then you just have to open it and keep the first 30 pages and the last 30 pages, and remove all the intermediate pages.
35 |
36 | ## Config
37 |
38 | Ramile automatically loads the config file `.ramileconfig.json` from the project root, if it exits. The file should be in json format. Possible config items as below:
39 |
40 | | Key | Description | Default | Example |
41 | | :--------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :--------------- |
42 | | ignore | Sets the directories/files to be ignored by Ramile. "ignore" paths should be sub directories/files under source_root. Any directories/files starting with any one of the "ignore" items will be ignored. Wildcars are not supported. | [] | ['Pods', 'libs'] |
43 | | source_root | Overwrites the root directory of source codes to avoid Ramile process from the project root. | '' | 'app' |
44 | | filters | Sets the exclusive filters (which means, all other extensions will NOT be processed) for file extensions. By default all files will be processed. | [] | ['.js', '.vue'] |
45 | | lines_to_extract | Sets the total lines to extract | 3000 | 3000 |
46 |
47 | ## Supported Languages
48 |
49 | | Language | Extensions |
50 | | :---------- | :-------------------- |
51 | | JavaScript | .js, .jsx, .vue, .wpy |
52 | | Java | .java |
53 | | PHP | .php |
54 | | HTML | .html, .htm |
55 | | CSS | .css, .less, .sass |
56 | | Swift | .swift |
57 | | Objective-C | .m |
58 |
59 | ## Test:
60 |
61 | ```shell
62 | cd tests
63 | pytest
64 | ```
65 |
--------------------------------------------------------------------------------