├── .github ├── FUNDING.yml └── workflows │ └── build_json.yml ├── LICENSE └── convert_readme.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://donate.freecodecamp.org/'] 2 | patreon: freecodecamp 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sourabh Joshi 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 | -------------------------------------------------------------------------------- /.github/workflows/build_json.yml: -------------------------------------------------------------------------------- 1 | name: Build emails.json file from README 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: [ main ] 6 | jobs: 7 | update: 8 | name: Update the emails.json file 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: Set up Python 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: '3.8' 18 | architecture: 'x64' 19 | 20 | - name: convert README to json 21 | run: | 22 | python convert_readme.py 23 | 24 | - name: setup git config 25 | run: | 26 | git config user.name "Quincy Larson Emails Bot" 27 | git config user.email "<>" 28 | echo ::set-output name=is_changed::$(git status --porcelain) 29 | 30 | - name: commit changes 31 | run: | 32 | if git status --porcelain | grep 'emails.json'; then 33 | echo "Commiting changed emails.json file" 34 | git add emails.json 35 | git commit -m "update emails.json" 36 | git push origin main 37 | else 38 | echo "No changes to be commited" 39 | fi 40 | -------------------------------------------------------------------------------- /convert_readme.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # Processes quotes from 5 | # https://github.com/sourabh-joshi/awesome-quincy-larson-emails 6 | # Use https://regex101.com to help create regular expressions 7 | # Usage: 8 | # $ python3 convert_readme.py 9 | 10 | import json 11 | import re 12 | 13 | IN_FILE = 'README.md' 14 | OUT_FILE = 'emails.json' 15 | 16 | with open(IN_FILE, 'r') as fh: 17 | texts = fh.readlines() 18 | texts = texts[5:] # Skip first lines with repository info 19 | texts = [x.strip() for x in texts] 20 | texts = [x for x in texts if x != ''] # Remove new lines 21 | 22 | with open(OUT_FILE, 'w') as fh: 23 | # Holds all data to be saved as JSON 24 | data = {} 25 | data['emails'] = [] 26 | first_pass = True 27 | 28 | for line in texts: 29 | # Replace some fancier quotes with normal ones 30 | line = re.sub('“|”', '"', line) 31 | line = re.sub('’', "'", line) 32 | 33 | # Look for dates which start with ### 34 | if re.match('^###', line): 35 | # First case when int_data doesn't exist 36 | if first_pass: 37 | first_pass = False 38 | int_data = {} 39 | else: 40 | # Add and rest data 41 | data['emails'].append(int_data) 42 | int_data = {} 43 | 44 | # Extract date information 45 | int_data['links'] = [] 46 | date_text = re.search('### (.*)', line).group(1) 47 | int_data['date'] = date_text 48 | 49 | # Links start with numbers 50 | elif re.search('^[0-9]', line): 51 | line = re.sub('–', '--', line) # Replace em-dash 52 | link_data = {} 53 | 54 | try: 55 | re_link = r'([0-9])\. (.*)\s+(https?://.*)?' 56 | result = re.search(re_link, line) 57 | 58 | link_data['order'] = result.group(1) 59 | link_data['link'] = result.group(3) 60 | 61 | description = result.group(2).strip(':') 62 | 63 | # Newer descriptions with descriptions ending with period 64 | # before parens with time to explore the link. 65 | if description[-1] == ')' and '. (' in description: 66 | info = re.search(r'(.*\.)\s?\(', description) 67 | link_data['description'] = info.group(1) 68 | 69 | # Edge case with some links only taking 1 minute. 70 | elif 'takes 1 minute' in description: 71 | info = re.search(r'(.*) \(takes 1 minute\)', description) 72 | link_data['description'] = info.group(1).strip() + '.' 73 | 74 | # Older links or variation of the link where there is no period 75 | # after the description and before the time to explore the 76 | # link. 77 | elif description[-1] == ')': 78 | info = re.search(r'(.*)\s?\((\d+|browsable)', description) 79 | link_data['description'] = info.group(1).strip() + '.' 80 | 81 | else: 82 | # Make a full sentence with period at the end to be 83 | # consistent with newer entries. 84 | link_data['description'] = description + '.' 85 | 86 | re_time = re.compile(r'(\d\.?\d*)\s+' 87 | r'(minute|hour)\s+' 88 | r'(read|YouTube|watch|course|video)') 89 | time_text = re_time.search(description) 90 | link_data['time_duration'] = time_text.group(1) 91 | link_data['time_type'] = time_text.group(2) + 's' # Plural 92 | 93 | # Edge case of one minute 94 | if 'takes 1 minute' in description: 95 | link_data['time_duration'] = '1' 96 | link_data['time_type'] = 'minutes' # Plural consistency 97 | 98 | except Exception: 99 | pass 100 | 101 | int_data['links'].append(link_data) 102 | 103 | elif re.search('^(Quote|This week)', line): 104 | line = re.sub('–', '-', line) # Replace en-dash 105 | line = re.sub('―', '-', line) # Replace em-dash 106 | line = re.sub('—', '-', line) # Replace other dash type 107 | line = re.sub(' ', '', line) # Replace odd space 108 | try: 109 | quote_info = re.search(r'\*\"(.*)\"\*\s*', line) 110 | int_data['quote'] = quote_info.group(1).strip() 111 | 112 | auth_info = re.search(r'\"\*\s*-\s*(.*)$', line) 113 | int_data['quote_author'] = auth_info.group(1).strip() 114 | except Exception: 115 | pass 116 | 117 | else: 118 | line = re.sub(u' — ', ' - ', line) 119 | int_data['bonus'] = line 120 | 121 | data['emails'].append(int_data) # Last case 122 | json.dump(data, fh, indent=2, sort_keys=True) 123 | --------------------------------------------------------------------------------