NML Says

Open Source Development 7 - Contributing

References for this Part

Chacon, Scott, Pro Git 2nd ed., https://git-scm.com/book/en/v2, also available in print from APress.

Today we shall look at chapter 6.

Model Solutions Previous Lessons

We still did not get anything from session 4.

We shall illustrate the work you did in session 6. It is all compiled into a repository at Codeberg, please find it at https://codeberg.org/arosano/library6

Example 1. README.md
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# Open Source Experiment
## Model Solutions to Exercise OSD.6

## Copyright
Copyright © 2025 Authors as they appear in the
file AUTHORS.

## License
Copyright as stated above

The software is released under the BSD-3 License
as described in the file LICENSE.

## Content
The file `lib6.py` contains a series of not 
necessarily related functions that may serve as 
useful utilities in various contexts.

When or if the number of functions grow it may
be useful to group them into separate subject
related library files.
Example 2. AUTHORS
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Copyright holder for individual functions as it
appears in the appropriate code files.

Copyright © 2025 PAUL MICKY D COSTA
Copyright © 2025 Sudeep Shrestha
Copyright © 2025 Rupesh Dulal
Copyright © 2025 Rabindra Kumar Sah
Copyright © 2025 Niels Müller Larsen
Copyright © 2025 Deepesh Gautam
Copyright © 2025 Muhammad
Copyright © 2025 Gurpreet Kaur
Copyright © 2025 Navneet
Copyright © 2025 Sandhya
Example 3. LICENSE
 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
Copyright 2025 Authors

Redistribution and use in source and binary forms, with
or without modification, are permitted provided that the
following conditions are met:

1.  Redistributions of source code must retain the above 
    copyright notice, this list of conditions and the 
    following disclaimer.

2.  Redistributions in binary form must reproduce the 
    above copyright notice, this list of conditions and
    the following disclaimer in the documentation and/or
    other materials provided with the distribution.

3.  Neither the name of the copyright holder nor the names
    of its contributors may be used to endorse or promote
    products derived from this software without specific 
    prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
“AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
Example 4. Compilation of Your Work - lib6.py
  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
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 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
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
'''
    OSD.6.0
    Copyright © 2025 PAUL MICKY D COSTA
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''
def isEvenInt(a):
    """Checks if the input is an even integer."""
    if isinstance(a, int) and a % 2 == 0:
        return True
    return False # nml changed from 'False'


'''
    OSD.6.1
    Copyright © 2025 Sudeep Shrestha
    Licensed under the BSD-3 License,
    Please refer to the LICENSE document.
    
'''
def f2c(f):
    """
    Convert temperature from Fahrenheit to Celsius.
    
    Args:
        f (float or int): Temperature in Fahrenheit
        
    Returns:
        float: Temperature in Celsius
    """
    return (f - 32) * 5 / 9


'''
    OSD.6.2 augmented by NML
    MIT License
    Copyright © 2025 Rupesh Dulal
'''
def tri_area(a,b,c):
    import math
#   return (a+b+c)/2
    s = (a + b + c) / 2
    return math.sqrt(s * (s - a) * (s - b) * (s - c))


'''
    OSD.6.3
    Copyright © 2025 Rabindra Kumar Sah
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''
def avg_list(arr):
    if not arr:
        return 0.0
    return float(sum(arr)) / len(arr)


'''
    OSD.6.4 Abhaya ?
    Copyright © 2025 NML
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''
def mul_list(arr):
    p = 1
    for item in arr:
        p = p * item
    return p


'''
    OSD.6.5
    Copyright © 2025 Deepesh Gautam
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''
def circ_area(r):
    import math
    return math.pi * r ** 2


'''
    OSD.6.6 should have been Sunil
    Copyright © 2025 Muhammad
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''
def fizz_buzz(n):
    if n % 3 == 0 and n % 5 == 0:
        return "fizzbuzz"
    elif n % 3 == 0:
        return "fizz"
    elif n % 5 == 0:
        return "buzz"
    else:
        return n


'''
    OSD.6.7 Muhammad (has sent prev)
    Copyright © 2025 NML
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''
def digit_sum(n, ds = 0):
	if n <= 0:
        return ds
    return digit_sum(n // 10, ds + n % 10)


'''
    OSD.6.8
    Copyright © 2025 Gurpreet Kaur
    Licensed under the BSD-3 License 
'''
def is_prime(num):
    if num < 2:
        return False
    if num in (2, 3):
        return True
    if num % 2 == 0 or num % 3 == 0:
        return False
    i = 5
    while i * i <= num:
        if num % i == 0 or num % (i + 2) == 0:
            return False
        i += 6
    return True

def largest_prime_under(n):
    for num in range(n - 1, 1, -1):
        if is_prime(num):
            return num
    return None  # No prime found (shouldn't happen for n > 2)


'''
    OSD.6.9
    Copyright © 2025 Navneet
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''
def largest_square_under(n):
    import math
    if n <= 0:
        return None  # Return None for non-positive inputs
    
    largest_square = int(math.sqrt(n - 1)) ** 2
    return largest_square


'''
    OSD.6.10
    Copyright © 2025 Sandhya
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''
def is_leap_year(yyyy):
    """Returns True if yyyy is a leap year, otherwise False."""
    if (yyyy % 4 == 0 and yyyy % 100 != 0) or (yyyy % 400 == 0):
        return True
    return False


'''
    OSD.6.11
    Copyright © 2025 PAUL MICKY D COSTA
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''
def factors(n):
    """Generates the prime factorization of a number as a formatted string."""
    if n <= 0:
        return "Please provide a positive integer."

    factor_counts = {}  # Dictionary to store factor counts
    divisor = 2  
    original_n = n  # Store the original number to check if it's prime

    while n >= divisor:
        if n % divisor == 0:
            factor_counts[divisor] = factor_counts.get(divisor, 0) + 1
            n //= divisor  
        else:
            divisor += 1  

    # If the number itself is prime, return "prime"
    if len(factor_counts) == 1 and list(factor_counts.keys())[0] == original_n:
        return "prime"

    # Formatting the output
    factor_list = [f"{p}^{factor_counts[p]}" if factor_counts[p] > 1 else str(p) for p in factor_counts]
    return "*".join(factor_list)


'''
    OSD.6.12 should have been Sudeep 
    Copyright © 2025 NML
    Licensed under the BSD-3 License,
    Please refer to the LICENSE document.
'''
def letter_freq(s, dic):
    '''
        receives a string of any length
        returns a sorted dictionary of letter frequencies
    '''
    import re
    s = s.lower()
    s = re.sub(r'[^\w]', '', s)

    for c in s:
        if c in dic:
            dic[c] = dic[c] + 1
        else:
            dic[c] = 1
    return dict(sorted(dic.items(), key=lambda item: item[0]))


'''
    OSD.6.13 should have been Rupesh
    Copyright © 2025 Sudeep Shrestha
    Licensed under the BSD-3 License,
    Please refer to the LICENSE document.
    
'''
def fibonacci(n):
    """
    Return the nth Fibonacci number (0-based index).
    
    Args:
        n (int): The position in the Fibonacci sequence (n >= 0)
        
    Returns:
        int: The nth Fibonacci number
        
    Raises:
        ValueError: If n is negative
    """
    if not isinstance(n, int):
        raise ValueError("Input must be an integer")
    if n < 0:
        raise ValueError("Input must be non-negative")
    
    
    if n == 0:
        return 0
    if n == 1:
    
    # Iteration
    a, b = 0, 1
    for _ in range(2, n + 1):
        a, b = b, a + b
    return b


'''
    OSD.6.14
    Copyright © 2025 <Rabindra Kumar Sah>
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''
import random
def roll(n):
    if n < 1:
        raise ValueError("Die must have at least 1 side")
    return random.randint(1, n)


'''
    OSD.6.15 should have been Abhaya
    MIT License
    Copyright © 2025 Rupesh Dulal
'''
import re
def validate_password(password):
    if len(password) < 16:
        return False
    if not any(char.isupper() for char in password):
        return False
    if not any(char.isdigit() for char in password):
        return False
    if not any(char in '!@#$%^&*()' for char in password):
        return False
    if not all(char.isalnum() or char.isspace() for char in password):  # Ensures only alphanumeric + spaces
        return False

    # Check for more than two consecutive occurrences of the same character
    if re.search(r"(.)\1\1", password):  # Matches any char repeated 3 times
        return False
    return True


'''
    OSD.6.16
    Copyright © 2025 Deepesh Gautam
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''
def is_anagram(s1, s2):
    return sorted(s1) == sorted(s2)
Example 5. Our Testsuite - testsuite.py
  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
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 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
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
'''
    testsuite.py
    
    Copyright © 2025 arosano/Niels Müller Larsen
    Licensed under the BSD-3 License,
    please refer to the LICENSE document
'''

import unittest
from lib6 import *

class Testing(unittest.TestCase):
    # OSD.6.0
    def test_isEven1(self):
        self.assertTrue(isEvenInt(2))
    def test_isEven2(self):
        self.assertTrue(isEvenInt(1024))
    def test_isEven3(self):
        self.assertFalse(isEvenInt(257))
    def test_isEven4(self):
        self.assertTrue(not isEvenInt(257))
    
    # OSD.6.1
    def test_f2c1(self):
        self.assertEqual(f2c(32), 0.0)
    def test_f2c2(self):
        self.assertEqual(f2c(212), 100.0)
    def test_f2c3(self):
        self.assertEqual(f2c(0), -17.77777777777778)
    def test_f2c4(self):
        self.assertEqual(f2c(100), 37.77777777777778)
    
    # OSD.6.2
    def test_tri_area0(self):
        self.assertEqual(tri_area(32, 20, 21), 204.9559403871964)
    def test_tri_area1(self):
        self.assertEqual(tri_area(3, 4, 5), 6.0)
    def test_tri_area2(self):
        self.assertEqual(tri_area(6, 8, 10), 24.0)
    
    # OSD.6.3
    def test_avg_list1(self):
        self.assertEqual(avg_list([32]), 32)
    def test_avg_list2(self):
        self.assertEqual(avg_list([32, 40]), 36)
    def test_avg_list3(self):
        self.assertEqual(avg_list([1,2,3,4,5,6]), 3.5)
    
    # OSD.6.4
    def test_mul_list1(self):
        self.assertEqual(mul_list([3, 4, 5]), 60.0)
    def test_mul_list2(self):
        self.assertEqual(mul_list([1,2,3,4,5,6]), 720)
    def test_mul_list3(self):
        self.assertEqual(mul_list([3,2,0]), 0)
    
    # OSD.6.5
    def test_circ_area(self):
        import math
        self.assertEqual(circ_area(32), math.pi*32*32)
    
    # OSD.6.6
    def test_fizzbuzz1(self):
        self.assertEqual(fizz_buzz(32), 32)
    def test_fizzbuzz2(self):
        self.assertEqual(fizz_buzz(45), 'fizzbuzz')
    def test_fizzbuzz3(self):
        self.assertEqual(fizz_buzz(48), 'fizz')
    def test_fizzbuzz4(self):
        self.assertEqual(fizz_buzz(50), 'buzz')
    
    # OSD.6.7
    def test_digit_sum0(self):
        self.assertEqual(digit_sum(32), 5)
    def test_digit_sum1(self):
        self.assertEqual(digit_sum(0), 0)
    def test_digit_sum2(self):
        self.assertEqual(digit_sum(123456), 21)
    
    # OSD.6.8
    def test_largest_prime_under0(self):
        self.assertEqual(largest_prime_under(256), 251)
    def test_largest_prime_under1(self):
        self.assertEqual(largest_prime_under(2**53-111), 9007199254740847)
    def test_largest_prime_under2(self):
        self.assertEqual(largest_prime_under(2**53-1), 9007199254740881)
    def test_largest_prime_under3(self):
        self.assertEqual(largest_prime_under(9223372036854775807), 9223372036854775783)
    
    # OSD.6.9
    def test_largest_square_under0(self):
        self.assertEqual(largest_square_under(32), 25)
    def test_largest_square_under1(self):
        self.assertEqual(largest_square_under(9223372036854775807), 9223372030926249001)
    
    # OSD.6.10
    def test_is_leap1(self):
        self.assertTrue(is_leap_year(2000))
    def test_is_leap2(self):
        self.assertFalse(is_leap_year(1900))
    
    # OSD.6.11
    def test_factors1(self):
        self.assertEqual(factors(60), '2^2*3*5')
    def test_factors2(self):
        self.assertEqual(factors(100), '2^2*5^2')
    def test_factors3(self):
        self.assertEqual(factors(2), 'prime')
    def test_factors4(self):
        self.assertEqual(factors(-10), 'Please provide a positive integer.')
    def test_factors5(self):
        self.assertEqual(factors(9007195909437503), '94906247*94906249')
    def test_factors6(self):
        self.assertEqual(factors(9223372036854775807), '7^2*73*127*337*92737*649657')
    
    # OSD.6.12
    def test_letterfreq(self):
        s1 = 'thequickbrownfoxjumpsoverthelazydog'
        d1 = {}
        d = {
            'a': 1,
            'b': 1,
            'c': 1,
            'd': 1,
            'e': 3,
            'f': 1,
            'g': 1,
            'h': 2,
            'i': 1,
            'j': 1,
            'k': 1,
            'l': 1,
            'm': 1,
            'n': 1,
            'o': 4,
            'p': 1,
            'q': 1,
            'r': 2,
            's': 1,
            't': 2,
            'u': 2,
            'v': 1,
            'w': 1,
            'x': 1,
            'y': 1,
            'z': 1,
        }
        # print(d1)
        self.assertEqual(letter_freq(s1, d1), d)
    
    # OSD.6.13
    def test_fibonacci1(self):
        self.assertEqual(fibonacci(2), 1)
    def test_fibonacci2(self):
        self.assertEqual(fibonacci(3), 2)
    def test_fibonacci3(self):
        self.assertEqual(fibonacci(4), 3)
    def test_fibonacci4(self):
        self.assertEqual(fibonacci(70), 190392490709135)
    def test_fibonacci5(self):
        self.assertEqual(fibonacci(71), 308061521170129)
    def test_fibonacci6(self):
        self.assertEqual(fibonacci(72), 498454011879264)
    
    # OSD.6.14
    def test_roll0(self):
        o = roll(17)
        self.assertTrue(o, 0 < o <= 17 )
    def test_roll1(self):
        o = roll(6)
        self.assertTrue(o, 0 < o <= 6 )
    def test_roll2(self):
        o = roll(2)
        self.assertTrue(o, 0 < o <= 2 )
    
    # OSD.6.15
    def test_validate_password0(self):
        self.assertFalse(validate_password('testtesttestttss'))
    def test_validate_password1(self):
        self.assertFalse(validate_password('test'))
    def test_validate_password2(self):
        self.assertTrue(validate_password('tes1!test testTest'))
    def test_validate_password3(self):
        self.assertTrue(validate_password('testTEST1234 hallo'))
    def test_validate_password4(self):
        self.assertFalse(validate_password('!#¤%/()/^@1234)%¤#'))
    
    # OSD.6.16
    def test_is_anagram(self):
        self.assertTrue(is_anagram('test', 'tset'))

if __name__ == '__main__':
    unittest.main()

Re Pro Git

We shall, as stated earlier, look at chapter 6, specifically 6.2 dealing with how we go about contributing to a project be means of forking.

The home page of the whole book is at https://git-scm.com/book/en/v2,

The section we shall look at, 6.2, is in the GitHub chapter, at https://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project

Let us dive in.

Today we shall stop at https://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project#_advanced_pull_requests and pick it up from there in out next session.

Exercises

Since next session is already tomorrow, there will be no exercises today. We are working on tomorrrows exercises :)