Browse code

add 'install-miniver' script

Also update README and setup.py to accomodate it.

Joseph Weston authored on 26/02/2018 23:54:00
Showing 3 changed files
... ...
@@ -24,10 +24,23 @@ different platforms and Python versions (yet).
24 24
 [pypi]: https://pypi.org/project/miniver/
25 25
 
26 26
 ## Usage
27
-Copy the contents of the `miniver` directory (in this repository) into your
28
-project's main package directory.
27
+The simplest way to use Miniver is to run the following in your project root:
28
+```
29
+curl https://raw.githubusercontent.com/jbweston/miniver/master/install-miniver | python - <your_package_directory>
30
+```
31
+This will grab the latest files from GitHub and set up Miniver for your project.
29 32
 
30
-Then copy the following snippets into the appropriate files:
33
+### I don't want to type that URL every time I use this
34
+You can `pip install miniver`, which will give you the `install-miniver` script.
35
+Then you can simply run the following from your project root to use Miniver:
36
+```
37
+install-miniver <your_package_directory>
38
+```
39
+
40
+### Can I use this without executing random code from the internet?
41
+Sure! Copy `miniver/_version.py` and `miniver/_static_version.py` from this
42
+repository into your package directory, then copy the following snippets into
43
+the appropriate files:
31 44
 
32 45
 ```python
33 46
 # Your package's __init__.py
... ...
@@ -69,11 +82,5 @@ which you copied the contents of `miniver`).
69 82
 
70 83
 That's it!
71 84
 
72
-**PROTIP**: When starting a new project you can just [download minver][zip]
73
-and use that as the starting point! You will still need to add
74
-`your_package/_static_version.py export-subst` to your `.gitattributes`.
75
-
76
-[zip]: https://github.com/jbweston/miniver/archive/master.zip
77
-
78 85
 ## License
79 86
 Miniver is in the public domain under a CC0 license.
80 87
new file mode 100755
... ...
@@ -0,0 +1,141 @@
1
+#!/usr/bin/env python3
2
+# This file is part of 'miniver': https://github.com/jbweston/miniver
3
+
4
+import sys
5
+import os
6
+import os.path
7
+import argparse
8
+import tempfile
9
+import shutil
10
+import textwrap
11
+from zipfile import ZipFile
12
+from importlib.util import find_spec
13
+from urllib.request import urlretrieve
14
+
15
+try:
16
+    import miniver
17
+    del miniver
18
+    _miniver_is_installed = True
19
+except ImportError:
20
+    _miniver_is_installed = False
21
+
22
+# When we fetch miniver from local files
23
+_miniver_modules = ('_version', '_static_version')
24
+
25
+
26
+# When we fetch miniver from GitHub
27
+_miniver_zip_url = 'https://github.com/jbweston/miniver/archive/master.zip'
28
+_zipfile_root = 'miniver-master'  # tied to the fact that we fetch master.zip
29
+
30
+# File templates
31
+_setup_template = '''
32
+    # Loads version.py module without importing the whole package.
33
+    def get_version_and_cmdclass(package_path):
34
+        import os
35
+        from importlib.util import module_from_spec, spec_from_file_location
36
+        spec = spec_from_file_location('version',
37
+                                       os.path.join(package_path, '_version.py'))
38
+        module = module_from_spec(spec)
39
+        spec.loader.exec_module(module)
40
+        return module.__version__, module.cmdclass
41
+
42
+
43
+    version, cmdclass = get_version_and_cmdclass('{package_dir}')
44
+
45
+    setup(
46
+        ...,
47
+        version=version,
48
+        cmdclass=cmdclass,
49
+    )
50
+'''
51
+
52
+_init_template = 'from ._version import __version__'
53
+_gitattribute_template = '{package_dir}/_static_version.py export-subst'
54
+
55
+
56
+def _line_in_file(to_find, filename):
57
+    """Return True if the specified line exists in the named file."""
58
+    assert '\n' not in to_find
59
+    try:
60
+        with open(filename) as f:
61
+            for line in f:
62
+                if to_find in line:
63
+                    return True
64
+            return False
65
+    except FileNotFoundError:
66
+        return False
67
+
68
+
69
+def _write_line(content, filename):
70
+    assert '\n' not in content
71
+    if not _line_in_file(content, filename):
72
+        with open(filename, 'a') as f:
73
+            f.write(content)
74
+
75
+
76
+def _fail(msg):
77
+    print(msg, file=sys.stderr)
78
+    print('Miniver was not installed', file=sys.stderr)
79
+    sys.exit(1)
80
+
81
+
82
+def extract_miniver_from_github():
83
+    filename, _ = urlretrieve(_miniver_zip_url)
84
+    z = ZipFile(filename)
85
+    tmpdir = tempfile.mkdtemp()
86
+    input_paths = ['/'.join((_zipfile_root, 'miniver', module+'.py'))
87
+                   for module in _miniver_modules]
88
+    for p in input_paths:
89
+        z.extract(p, path=tmpdir)
90
+    return [os.path.join(tmpdir, *p.split()) for p in input_paths]
91
+
92
+
93
+def extract_miniver_from_local():
94
+    return [find_spec('.' + module, package='miniver').origin
95
+            for module in _miniver_modules]
96
+
97
+
98
+def get_args():
99
+    parser = argparse.ArgumentParser(
100
+        description="Install 'miniver' into the current Python package")
101
+    parser.add_argument('package_directory',
102
+                        help="Directory to install 'miniver' into.")
103
+    return parser.parse_args().package_directory
104
+
105
+
106
+def main():
107
+    package_dir = get_args()
108
+    if not os.path.isdir(package_dir):
109
+        _fail("Directory '{}' does not exist".format(package_dir))
110
+    if package_dir != os.path.relpath(package_dir):
111
+        _fail("'{}' is not a relative directory".format(package_dir))
112
+
113
+    # Get miniver files
114
+    if _miniver_is_installed:
115
+        miniver_paths = extract_miniver_from_local()
116
+    else:
117
+        miniver_paths = extract_miniver_from_github()
118
+    output_paths = [os.path.join(package_dir, os.path.basename(path))
119
+                    for path in miniver_paths]
120
+
121
+    for path in output_paths:
122
+        if os.path.exists(path):
123
+            _fail("'{}' already exists".format(path))
124
+
125
+    # Write content to local package directory
126
+    for path, output_path in zip(miniver_paths, output_paths):
127
+        shutil.copy(path, output_path)
128
+    _write_line(_gitattribute_template.format(package_dir=package_dir),
129
+                '.gitattributes')
130
+    _write_line(_init_template.format(package_dir=package_dir),
131
+                os.path.join(package_dir, '__init__.py'))
132
+
133
+    msg = '\n'.join(textwrap.wrap(
134
+        "Miniver is installed into '{package_dir}/'. "
135
+        "You still have to copy the following snippet into your 'setup.py':"
136
+    ))
137
+    print('\n'.join((msg, _setup_template)).format(package_dir=package_dir))
138
+
139
+
140
+if __name__ == '__main__':
141
+    main()
... ...
@@ -35,4 +35,5 @@ setup(
35 35
     ],
36 36
     packages=find_packages('.'),
37 37
     cmdclass=cmdclass,
38
+    scripts=['install-miniver']
38 39
 )