├── 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 | --------------------------------------------------------------------------------