Merge lp:~developer-ubuntu-com-dev/developer-ubuntu-com/soon-trunk into lp:developer-ubuntu-com
- soon-trunk
- Merge into stable
Proposed by
Daniel Holbach
Status: | Merged |
---|---|
Approved by: | David Callé |
Approved revision: | 194 |
Merged at revision: | 190 |
Proposed branch: | lp:~developer-ubuntu-com-dev/developer-ubuntu-com/soon-trunk |
Merge into: | lp:developer-ubuntu-com |
Diff against target: |
8893 lines (+6790/-1259) 78 files modified
Makefile (+5/-5) README.md (+1/-1) api_docs/migrations/0001_initial.py (+138/-185) developer_portal/admin.py (+1/-16) developer_portal/blog/views.py (+18/-6) developer_portal/management/commands/initdb.py (+26/-20) developer_portal/management/commands/update-template.py (+13/-16) developer_portal/migrations/0001_initial.py (+0/-20) developer_portal/migrations/0002_add_rawhtml_plugin.py (+0/-53) developer_portal/migrations/0003_add_external_docs_branches.py (+0/-62) developer_portal/migrations/0004_auto__add_seoextension.py (+0/-126) developer_portal/models.py (+0/-21) developer_portal/settings.py (+44/-22) developer_portal/urls.py (+1/-1) locale/developer_portal.pot (+69/-54) md_importer/TODO (+7/-0) md_importer/admin.py (+36/-0) md_importer/importer/__init__.py (+5/-0) md_importer/importer/article.py (+163/-0) md_importer/importer/process.py (+60/-0) md_importer/importer/publish.py (+75/-0) md_importer/importer/repo.py (+174/-0) md_importer/importer/source.py (+60/-0) md_importer/management/commands/import_md.py (+15/-344) md_importer/migrations/0001_initial.py (+46/-0) md_importer/models.py (+56/-0) md_importer/tests/data/link-broken-test/file1.md (+5/-0) md_importer/tests/data/link-broken-test/file2.md (+3/-0) md_importer/tests/data/link-test/file1.md (+5/-0) md_importer/tests/data/link-test/file2.md (+3/-0) md_importer/tests/data/link2-test/file1.md (+5/-0) md_importer/tests/data/link2-test/file3.md (+3/-0) md_importer/tests/data/local-image-test/test1.md (+6/-0) md_importer/tests/data/remote-image-test/test1.md (+6/-0) md_importer/tests/data/snapcraft-test/CONTRIBUTING.md (+32/-0) md_importer/tests/data/snapcraft-test/HACKING.md (+48/-0) md_importer/tests/data/snapcraft-test/README.md (+33/-0) md_importer/tests/data/snapcraft-test/docs/get-started.md (+47/-0) md_importer/tests/data/snapcraft-test/docs/intro.md (+104/-0) md_importer/tests/data/snapcraft-test/docs/ros-snap.md (+366/-0) md_importer/tests/data/snapcraft-test/docs/snapcraft overview.svg (+1199/-0) md_importer/tests/data/snapcraft-test/docs/snapcraft-advanced-features.md (+233/-0) md_importer/tests/data/snapcraft-test/docs/snapcraft-parts.md (+130/-0) md_importer/tests/data/snapcraft-test/docs/snapcraft-syntax.md (+83/-0) md_importer/tests/data/snapcraft-test/docs/snapcraft-usage.md (+43/-0) md_importer/tests/data/snapcraft-test/docs/your-first-snap.md (+318/-0) md_importer/tests/data/snappy-test/README.md (+119/-0) md_importer/tests/data/snappy-test/docs/autoupdate.md (+48/-0) md_importer/tests/data/snappy-test/docs/config.md (+128/-0) md_importer/tests/data/snappy-test/docs/cross-build.md (+18/-0) md_importer/tests/data/snappy-test/docs/frameworks.md (+256/-0) md_importer/tests/data/snappy-test/docs/gadget.md (+244/-0) md_importer/tests/data/snappy-test/docs/garbage.md (+93/-0) md_importer/tests/data/snappy-test/docs/hashes.md (+30/-0) md_importer/tests/data/snappy-test/docs/meta.md (+113/-0) md_importer/tests/data/snappy-test/docs/package-names.md (+138/-0) md_importer/tests/data/snappy-test/docs/rest.md (+603/-0) md_importer/tests/data/snappy-test/docs/security.md (+233/-0) md_importer/tests/data/snappy-test/docs/system-updates.rst (+421/-0) md_importer/tests/test_branch_fetch.py (+47/-0) md_importer/tests/test_branch_import.py (+118/-0) md_importer/tests/test_import_process.py (+68/-0) md_importer/tests/test_link_rewrite.py (+86/-0) md_importer/tests/test_misc.py (+35/-0) md_importer/tests/test_snappy_import.py (+61/-0) md_importer/tests/test_utils.py (+50/-0) md_importer/tests/utils.py (+76/-0) pip-cache-revno.txt (+1/-1) requirements.txt (+43/-43) service/urls.py (+7/-7) store_data/migrations/0001_initial.py (+62/-133) store_data/migrations/0002_add_title_field.py (+0/-58) store_data/migrations/0003_add_website_field.py (+0/-59) templates/zinnia/_entry_detail_base.html (+2/-2) templates/zinnia/base.html (+1/-1) templates/zinnia/category_list.html (+2/-1) templates/zinnia/entry_detail_base.html (+1/-1) templates/zinnia/entry_list.html (+1/-1) |
To merge this branch: | bzr merge lp:~developer-ubuntu-com-dev/developer-ubuntu-com/soon-trunk |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
David Callé | Pending | ||
Michael Hall | Pending | ||
Daniel Holbach | Pending | ||
Ubuntu App Developer site developers | Pending | ||
Review via email: mp+283019@code.launchpad.net |
Commit message
Description of the change
This is an MP which bundles the changes in
- lp:~developer-ubuntu-com-dev/developer-ubuntu-com/django-1.8-cms-2.3
- lp:~dholbach/developer-ubuntu-com/rework-importer
and fixes issues because of changes which were backed out previously.
This should be reviewed and tested carefully by all of us.
To post a comment you must log in.
Revision history for this message
David Callé (davidc3) wrote : | # |
Revision history for this message
Daniel Holbach (dholbach) wrote : | # |
pip-cache needs an update to add pymdown-extensions (for markdown tables, fenced code blocks, etc.)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' | |||
2 | --- Makefile 2016-01-12 14:24:40 +0000 | |||
3 | +++ Makefile 2016-01-19 00:21:38 +0000 | |||
4 | @@ -46,7 +46,7 @@ | |||
5 | 46 | 46 | ||
6 | 47 | syncdb: | 47 | syncdb: |
7 | 48 | @echo "Syncing database" | 48 | @echo "Syncing database" |
9 | 49 | @python manage.py syncdb --noinput --migrate --settings charm_settings | 49 | @python manage.py migrate --noinput --settings charm_settings |
10 | 50 | 50 | ||
11 | 51 | collectstatic: collectstatic.done | 51 | collectstatic: collectstatic.done |
12 | 52 | collectstatic.done: | 52 | collectstatic.done: |
13 | @@ -61,18 +61,18 @@ | |||
14 | 61 | update-pip-cache: | 61 | update-pip-cache: |
15 | 62 | @echo "Updating pip-cache" | 62 | @echo "Updating pip-cache" |
16 | 63 | rm -rf pip-cache | 63 | rm -rf pip-cache |
18 | 64 | bzr branch lp:~developer-ubuntu-com-dev/developer-ubuntu-com/dependencies pip-cache | 64 | bzr branch lp:developer-ubuntu-com/dependencies pip-cache |
19 | 65 | pip install --exists-action=w --download pip-cache/ -r requirements.txt | 65 | pip install --exists-action=w --download pip-cache/ -r requirements.txt |
20 | 66 | bzr add pip-cache/* | 66 | bzr add pip-cache/* |
21 | 67 | bzr commit pip-cache/ -m 'automatically updated devportal requirements' | 67 | bzr commit pip-cache/ -m 'automatically updated devportal requirements' |
23 | 68 | bzr push --directory pip-cache lp:~developer-ubuntu-com-dev/developer-ubuntu-com/dependencies | 68 | bzr push --directory pip-cache lp:developer-ubuntu-com/dependencies |
24 | 69 | bzr revno pip-cache > pip-cache-revno.txt | 69 | bzr revno pip-cache > pip-cache-revno.txt |
25 | 70 | rm -rf pip-cache | 70 | rm -rf pip-cache |
26 | 71 | @echo "** Remember to commit pip-cache-revno.txt" | 71 | @echo "** Remember to commit pip-cache-revno.txt" |
27 | 72 | 72 | ||
28 | 73 | pip-cache: | 73 | pip-cache: |
29 | 74 | @echo "Downloading pip-cache" | 74 | @echo "Downloading pip-cache" |
31 | 75 | @bzr branch -r `cat pip-cache-revno.txt` lp:~developer-ubuntu-com-dev/developer-ubuntu-com/dependencies pip-cache | 75 | @bzr branch -r `cat pip-cache-revno.txt` lp:developer-ubuntu-com/dependencies pip-cache |
32 | 76 | 76 | ||
33 | 77 | env: pip-cache | 77 | env: pip-cache |
34 | 78 | @echo "Creating virtualenv" | 78 | @echo "Creating virtualenv" |
35 | @@ -82,7 +82,7 @@ | |||
36 | 82 | 82 | ||
37 | 83 | db.sqlite3: env | 83 | db.sqlite3: env |
38 | 84 | @echo "Initializing database" | 84 | @echo "Initializing database" |
40 | 85 | @./env/bin/python manage.py syncdb --noinput --migrate | 85 | @./env/bin/python manage.py migrate --noinput |
41 | 86 | @./env/bin/python manage.py initdb | 86 | @./env/bin/python manage.py initdb |
42 | 87 | @./env/bin/python manage.py init_apidocs | 87 | @./env/bin/python manage.py init_apidocs |
43 | 88 | 88 | ||
44 | 89 | 89 | ||
45 | === modified file 'README.md' | |||
46 | --- README.md 2015-12-08 10:25:29 +0000 | |||
47 | +++ README.md 2016-01-19 00:21:38 +0000 | |||
48 | @@ -27,7 +27,7 @@ | |||
49 | 27 | ./env/bin/python manage.py flush --noinput | 27 | ./env/bin/python manage.py flush --noinput |
50 | 28 | echo "delete from auth_permission;" | ./env/bin/python manage.py dbshell | 28 | echo "delete from auth_permission;" | ./env/bin/python manage.py dbshell |
51 | 29 | ./env/bin/python manage.py loaddata ../dbbackup/dbdump.json | 29 | ./env/bin/python manage.py loaddata ../dbbackup/dbdump.json |
53 | 30 | ./env/bin/python manage.py syncdb --noinput --all | 30 | ./env/bin/python manage.py migrate --noinput |
54 | 31 | ./env/bin/python manage.py initdb | 31 | ./env/bin/python manage.py initdb |
55 | 32 | 32 | ||
56 | 33 | # Managing translations | 33 | # Managing translations |
57 | 34 | 34 | ||
58 | === modified file 'api_docs/migrations/0001_initial.py' | |||
59 | --- api_docs/migrations/0001_initial.py 2015-12-08 10:25:29 +0000 | |||
60 | +++ api_docs/migrations/0001_initial.py 2016-01-19 00:21:38 +0000 | |||
61 | @@ -1,186 +1,139 @@ | |||
62 | 1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
63 | 2 | from south.utils import datetime_utils as datetime | ||
64 | 3 | from south.db import db | ||
65 | 4 | from south.v2 import SchemaMigration | ||
66 | 5 | from django.db import models | ||
67 | 6 | |||
68 | 7 | |||
69 | 8 | class Migration(SchemaMigration): | ||
70 | 9 | |||
71 | 10 | def forwards(self, orm): | ||
72 | 11 | # Adding model 'Topic' | ||
73 | 12 | db.create_table(u'api_docs_topic', ( | ||
74 | 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), | ||
75 | 14 | ('name', self.gf('django.db.models.fields.CharField')(max_length=64)), | ||
76 | 15 | ('slug', self.gf('django.db.models.fields.CharField')(max_length=64)), | ||
77 | 16 | )) | ||
78 | 17 | db.send_create_signal(u'api_docs', ['Topic']) | ||
79 | 18 | |||
80 | 19 | # Adding model 'Language' | ||
81 | 20 | db.create_table(u'api_docs_language', ( | ||
82 | 21 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), | ||
83 | 22 | ('topic', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['api_docs.Topic'])), | ||
84 | 23 | ('name', self.gf('django.db.models.fields.CharField')(max_length=64)), | ||
85 | 24 | ('slug', self.gf('django.db.models.fields.CharField')(max_length=64)), | ||
86 | 25 | ('current_version', self.gf('django.db.models.fields.related.ForeignKey')(related_name='current_for_lang', null=True, to=orm['api_docs.Version'])), | ||
87 | 26 | ('development_version', self.gf('django.db.models.fields.related.ForeignKey')(related_name='development_for_lang', null=True, to=orm['api_docs.Version'])), | ||
88 | 27 | )) | ||
89 | 28 | db.send_create_signal(u'api_docs', ['Language']) | ||
90 | 29 | |||
91 | 30 | # Adding model 'Version' | ||
92 | 31 | db.create_table(u'api_docs_version', ( | ||
93 | 32 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), | ||
94 | 33 | ('language', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['api_docs.Language'], null=True)), | ||
95 | 34 | ('name', self.gf('django.db.models.fields.CharField')(max_length=64)), | ||
96 | 35 | ('slug', self.gf('django.db.models.fields.CharField')(max_length=64)), | ||
97 | 36 | )) | ||
98 | 37 | db.send_create_signal(u'api_docs', ['Version']) | ||
99 | 38 | |||
100 | 39 | # Adding model 'Section' | ||
101 | 40 | db.create_table(u'api_docs_section', ( | ||
102 | 41 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), | ||
103 | 42 | ('name', self.gf('django.db.models.fields.CharField')(max_length=64)), | ||
104 | 43 | ('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), | ||
105 | 44 | ('topic_version', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['api_docs.Version'])), | ||
106 | 45 | )) | ||
107 | 46 | db.send_create_signal(u'api_docs', ['Section']) | ||
108 | 47 | |||
109 | 48 | # Adding model 'Namespace' | ||
110 | 49 | db.create_table(u'api_docs_namespace', ( | ||
111 | 50 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), | ||
112 | 51 | ('platform_section', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['api_docs.Section'])), | ||
113 | 52 | ('name', self.gf('django.db.models.fields.CharField')(max_length=64)), | ||
114 | 53 | ('display_name', self.gf('django.db.models.fields.CharField')(default='', max_length=64, blank=True)), | ||
115 | 54 | ('data', self.gf('django.db.models.fields.TextField')(default='', blank=True)), | ||
116 | 55 | ('source_file', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), | ||
117 | 56 | ('source_format', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)), | ||
118 | 57 | )) | ||
119 | 58 | db.send_create_signal(u'api_docs', ['Namespace']) | ||
120 | 59 | |||
121 | 60 | # Adding model 'Element' | ||
122 | 61 | db.create_table(u'api_docs_element', ( | ||
123 | 62 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), | ||
124 | 63 | ('name', self.gf('django.db.models.fields.CharField')(max_length=64)), | ||
125 | 64 | ('description', self.gf('django.db.models.fields.CharField')(default='', max_length=256, blank=True)), | ||
126 | 65 | ('namespace', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['api_docs.Namespace'], null=True, blank=True)), | ||
127 | 66 | ('section', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['api_docs.Section'])), | ||
128 | 67 | ('fullname', self.gf('django.db.models.fields.CharField')(max_length=128)), | ||
129 | 68 | ('keywords', self.gf('django.db.models.fields.CharField')(default='', max_length=256, blank=True)), | ||
130 | 69 | ('data', self.gf('django.db.models.fields.TextField')(default='', blank=True)), | ||
131 | 70 | ('source_file', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), | ||
132 | 71 | ('source_format', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)), | ||
133 | 72 | )) | ||
134 | 73 | db.send_create_signal(u'api_docs', ['Element']) | ||
135 | 74 | |||
136 | 75 | # Adding model 'Page' | ||
137 | 76 | db.create_table(u'api_docs_page', ( | ||
138 | 77 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), | ||
139 | 78 | ('slug', self.gf('django.db.models.fields.CharField')(max_length=64)), | ||
140 | 79 | ('title', self.gf('django.db.models.fields.CharField')(max_length=64)), | ||
141 | 80 | ('description', self.gf('django.db.models.fields.CharField')(default='', max_length=256, blank=True)), | ||
142 | 81 | ('namespace', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['api_docs.Namespace'], null=True, blank=True)), | ||
143 | 82 | ('section', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['api_docs.Section'])), | ||
144 | 83 | ('fullname', self.gf('django.db.models.fields.CharField')(max_length=128)), | ||
145 | 84 | ('keywords', self.gf('django.db.models.fields.CharField')(default='', max_length=256, blank=True)), | ||
146 | 85 | ('data', self.gf('django.db.models.fields.TextField')(default='', blank=True)), | ||
147 | 86 | ('source_file', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), | ||
148 | 87 | ('source_format', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)), | ||
149 | 88 | ('order_index', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, blank=True)), | ||
150 | 89 | )) | ||
151 | 90 | db.send_create_signal(u'api_docs', ['Page']) | ||
152 | 91 | |||
153 | 92 | |||
154 | 93 | def backwards(self, orm): | ||
155 | 94 | # Deleting model 'Topic' | ||
156 | 95 | db.delete_table(u'api_docs_topic') | ||
157 | 96 | |||
158 | 97 | # Deleting model 'Language' | ||
159 | 98 | db.delete_table(u'api_docs_language') | ||
160 | 99 | |||
161 | 100 | # Deleting model 'Version' | ||
162 | 101 | db.delete_table(u'api_docs_version') | ||
163 | 102 | |||
164 | 103 | # Deleting model 'Section' | ||
165 | 104 | db.delete_table(u'api_docs_section') | ||
166 | 105 | |||
167 | 106 | # Deleting model 'Namespace' | ||
168 | 107 | db.delete_table(u'api_docs_namespace') | ||
169 | 108 | |||
170 | 109 | # Deleting model 'Element' | ||
171 | 110 | db.delete_table(u'api_docs_element') | ||
172 | 111 | |||
173 | 112 | # Deleting model 'Page' | ||
174 | 113 | db.delete_table(u'api_docs_page') | ||
175 | 114 | |||
176 | 115 | |||
177 | 116 | models = { | ||
178 | 117 | u'api_docs.element': { | ||
179 | 118 | 'Meta': {'ordering': "('name',)", 'object_name': 'Element'}, | ||
180 | 119 | 'data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), | ||
181 | 120 | 'description': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}), | ||
182 | 121 | 'fullname': ('django.db.models.fields.CharField', [], {'max_length': '128'}), | ||
183 | 122 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
184 | 123 | 'keywords': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}), | ||
185 | 124 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), | ||
186 | 125 | 'namespace': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['api_docs.Namespace']", 'null': 'True', 'blank': 'True'}), | ||
187 | 126 | 'section': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['api_docs.Section']"}), | ||
188 | 127 | 'source_file': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
189 | 128 | 'source_format': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}) | ||
190 | 129 | }, | ||
191 | 130 | u'api_docs.language': { | ||
192 | 131 | 'Meta': {'object_name': 'Language'}, | ||
193 | 132 | 'current_version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'current_for_lang'", 'null': 'True', 'to': u"orm['api_docs.Version']"}), | ||
194 | 133 | 'development_version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'development_for_lang'", 'null': 'True', 'to': u"orm['api_docs.Version']"}), | ||
195 | 134 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
196 | 135 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), | ||
197 | 136 | 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64'}), | ||
198 | 137 | 'topic': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['api_docs.Topic']"}) | ||
199 | 138 | }, | ||
200 | 139 | u'api_docs.namespace': { | ||
201 | 140 | 'Meta': {'ordering': "('name',)", 'object_name': 'Namespace'}, | ||
202 | 141 | 'data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), | ||
203 | 142 | 'display_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'blank': 'True'}), | ||
204 | 143 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
205 | 144 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), | ||
206 | 145 | 'platform_section': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['api_docs.Section']"}), | ||
207 | 146 | 'source_file': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
208 | 147 | 'source_format': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}) | ||
209 | 148 | }, | ||
210 | 149 | u'api_docs.page': { | ||
211 | 150 | 'Meta': {'ordering': "('order_index',)", 'object_name': 'Page'}, | ||
212 | 151 | 'data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), | ||
213 | 152 | 'description': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}), | ||
214 | 153 | 'fullname': ('django.db.models.fields.CharField', [], {'max_length': '128'}), | ||
215 | 154 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
216 | 155 | 'keywords': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}), | ||
217 | 156 | 'namespace': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['api_docs.Namespace']", 'null': 'True', 'blank': 'True'}), | ||
218 | 157 | 'order_index': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}), | ||
219 | 158 | 'section': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['api_docs.Section']"}), | ||
220 | 159 | 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64'}), | ||
221 | 160 | 'source_file': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
222 | 161 | 'source_format': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), | ||
223 | 162 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '64'}) | ||
224 | 163 | }, | ||
225 | 164 | u'api_docs.section': { | ||
226 | 165 | 'Meta': {'object_name': 'Section'}, | ||
227 | 166 | 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), | ||
228 | 167 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
229 | 168 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), | ||
230 | 169 | 'topic_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['api_docs.Version']"}) | ||
231 | 170 | }, | ||
232 | 171 | u'api_docs.topic': { | ||
233 | 172 | 'Meta': {'object_name': 'Topic'}, | ||
234 | 173 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
235 | 174 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), | ||
236 | 175 | 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64'}) | ||
237 | 176 | }, | ||
238 | 177 | u'api_docs.version': { | ||
239 | 178 | 'Meta': {'object_name': 'Version'}, | ||
240 | 179 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
241 | 180 | 'language': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['api_docs.Language']", 'null': 'True'}), | ||
242 | 181 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), | ||
243 | 182 | 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64'}) | ||
244 | 183 | } | ||
245 | 184 | } | ||
246 | 185 | |||
247 | 186 | complete_apps = ['api_docs'] | ||
248 | 187 | \ No newline at end of file | 2 | \ No newline at end of file |
249 | 3 | from __future__ import unicode_literals | ||
250 | 4 | |||
251 | 5 | from django.db import migrations, models | ||
252 | 6 | |||
253 | 7 | |||
254 | 8 | class Migration(migrations.Migration): | ||
255 | 9 | |||
256 | 10 | dependencies = [ | ||
257 | 11 | ] | ||
258 | 12 | |||
259 | 13 | operations = [ | ||
260 | 14 | migrations.CreateModel( | ||
261 | 15 | name='Element', | ||
262 | 16 | fields=[ | ||
263 | 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | ||
264 | 18 | ('name', models.CharField(max_length=64)), | ||
265 | 19 | ('description', models.CharField(default=b'', max_length=256, blank=True)), | ||
266 | 20 | ('fullname', models.CharField(max_length=128)), | ||
267 | 21 | ('keywords', models.CharField(default=b'', max_length=256, blank=True)), | ||
268 | 22 | ('data', models.TextField(default=b'', blank=True)), | ||
269 | 23 | ('source_file', models.CharField(max_length=128, null=True, blank=True)), | ||
270 | 24 | ('source_format', models.CharField(max_length=32, null=True, blank=True)), | ||
271 | 25 | ], | ||
272 | 26 | options={ | ||
273 | 27 | 'ordering': ('name',), | ||
274 | 28 | 'verbose_name': 'Rendered Element', | ||
275 | 29 | 'verbose_name_plural': 'Rendered Elements', | ||
276 | 30 | }, | ||
277 | 31 | ), | ||
278 | 32 | migrations.CreateModel( | ||
279 | 33 | name='Language', | ||
280 | 34 | fields=[ | ||
281 | 35 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | ||
282 | 36 | ('name', models.CharField(max_length=64)), | ||
283 | 37 | ('slug', models.CharField(max_length=64)), | ||
284 | 38 | ], | ||
285 | 39 | ), | ||
286 | 40 | migrations.CreateModel( | ||
287 | 41 | name='Namespace', | ||
288 | 42 | fields=[ | ||
289 | 43 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | ||
290 | 44 | ('name', models.CharField(max_length=64)), | ||
291 | 45 | ('display_name', models.CharField(default=b'', max_length=64, blank=True)), | ||
292 | 46 | ('data', models.TextField(default=b'', blank=True)), | ||
293 | 47 | ('source_file', models.CharField(max_length=128, null=True, blank=True)), | ||
294 | 48 | ('source_format', models.CharField(max_length=32, null=True, blank=True)), | ||
295 | 49 | ], | ||
296 | 50 | options={ | ||
297 | 51 | 'ordering': ('name',), | ||
298 | 52 | }, | ||
299 | 53 | ), | ||
300 | 54 | migrations.CreateModel( | ||
301 | 55 | name='Page', | ||
302 | 56 | fields=[ | ||
303 | 57 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | ||
304 | 58 | ('slug', models.CharField(max_length=64)), | ||
305 | 59 | ('title', models.CharField(max_length=64)), | ||
306 | 60 | ('description', models.CharField(default=b'', max_length=256, blank=True)), | ||
307 | 61 | ('fullname', models.CharField(max_length=128)), | ||
308 | 62 | ('keywords', models.CharField(default=b'', max_length=256, blank=True)), | ||
309 | 63 | ('data', models.TextField(default=b'', blank=True)), | ||
310 | 64 | ('source_file', models.CharField(max_length=128, null=True, blank=True)), | ||
311 | 65 | ('source_format', models.CharField(max_length=32, null=True, blank=True)), | ||
312 | 66 | ('order_index', models.PositiveIntegerField(default=0, blank=True)), | ||
313 | 67 | ('namespace', models.ForeignKey(blank=True, to='api_docs.Namespace', null=True)), | ||
314 | 68 | ], | ||
315 | 69 | options={ | ||
316 | 70 | 'ordering': ('order_index',), | ||
317 | 71 | 'verbose_name': 'Rendered Page', | ||
318 | 72 | 'verbose_name_plural': 'Rendered Pages', | ||
319 | 73 | }, | ||
320 | 74 | ), | ||
321 | 75 | migrations.CreateModel( | ||
322 | 76 | name='Section', | ||
323 | 77 | fields=[ | ||
324 | 78 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | ||
325 | 79 | ('name', models.CharField(max_length=64)), | ||
326 | 80 | ('description', models.TextField(null=True, blank=True)), | ||
327 | 81 | ], | ||
328 | 82 | ), | ||
329 | 83 | migrations.CreateModel( | ||
330 | 84 | name='Topic', | ||
331 | 85 | fields=[ | ||
332 | 86 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | ||
333 | 87 | ('name', models.CharField(max_length=64)), | ||
334 | 88 | ('slug', models.CharField(max_length=64)), | ||
335 | 89 | ], | ||
336 | 90 | ), | ||
337 | 91 | migrations.CreateModel( | ||
338 | 92 | name='Version', | ||
339 | 93 | fields=[ | ||
340 | 94 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | ||
341 | 95 | ('name', models.CharField(max_length=64)), | ||
342 | 96 | ('slug', models.CharField(max_length=64)), | ||
343 | 97 | ('language', models.ForeignKey(to='api_docs.Language', null=True)), | ||
344 | 98 | ], | ||
345 | 99 | ), | ||
346 | 100 | migrations.AddField( | ||
347 | 101 | model_name='section', | ||
348 | 102 | name='topic_version', | ||
349 | 103 | field=models.ForeignKey(to='api_docs.Version'), | ||
350 | 104 | ), | ||
351 | 105 | migrations.AddField( | ||
352 | 106 | model_name='page', | ||
353 | 107 | name='section', | ||
354 | 108 | field=models.ForeignKey(to='api_docs.Section'), | ||
355 | 109 | ), | ||
356 | 110 | migrations.AddField( | ||
357 | 111 | model_name='namespace', | ||
358 | 112 | name='platform_section', | ||
359 | 113 | field=models.ForeignKey(to='api_docs.Section'), | ||
360 | 114 | ), | ||
361 | 115 | migrations.AddField( | ||
362 | 116 | model_name='language', | ||
363 | 117 | name='current_version', | ||
364 | 118 | field=models.ForeignKey(related_name='current_for_lang', blank=True, to='api_docs.Version', null=True), | ||
365 | 119 | ), | ||
366 | 120 | migrations.AddField( | ||
367 | 121 | model_name='language', | ||
368 | 122 | name='development_version', | ||
369 | 123 | field=models.ForeignKey(related_name='development_for_lang', blank=True, to='api_docs.Version', null=True), | ||
370 | 124 | ), | ||
371 | 125 | migrations.AddField( | ||
372 | 126 | model_name='language', | ||
373 | 127 | name='topic', | ||
374 | 128 | field=models.ForeignKey(to='api_docs.Topic'), | ||
375 | 129 | ), | ||
376 | 130 | migrations.AddField( | ||
377 | 131 | model_name='element', | ||
378 | 132 | name='namespace', | ||
379 | 133 | field=models.ForeignKey(blank=True, to='api_docs.Namespace', null=True), | ||
380 | 134 | ), | ||
381 | 135 | migrations.AddField( | ||
382 | 136 | model_name='element', | ||
383 | 137 | name='section', | ||
384 | 138 | field=models.ForeignKey(to='api_docs.Section'), | ||
385 | 139 | ), | ||
386 | 140 | ] | ||
387 | 188 | 141 | ||
388 | === modified file 'developer_portal/admin.py' | |||
389 | --- developer_portal/admin.py 2015-12-08 10:25:29 +0000 | |||
390 | +++ developer_portal/admin.py 2016-01-19 00:21:38 +0000 | |||
391 | @@ -4,20 +4,12 @@ | |||
392 | 4 | from reversion.admin import VersionAdmin | 4 | from reversion.admin import VersionAdmin |
393 | 5 | 5 | ||
394 | 6 | from cms.extensions import TitleExtensionAdmin | 6 | from cms.extensions import TitleExtensionAdmin |
397 | 7 | from .models import ExternalDocsBranch, SEOExtension | 7 | from .models import SEOExtension |
396 | 8 | from django.core.management import call_command | ||
398 | 9 | 8 | ||
399 | 10 | __all__ = ( | 9 | __all__ = ( |
400 | 11 | ) | 10 | ) |
401 | 12 | 11 | ||
402 | 13 | 12 | ||
403 | 14 | def import_selected_external_docs_branches(modeladmin, request, queryset): | ||
404 | 15 | for branch in queryset: | ||
405 | 16 | call_command('import-external-docs-branches', branch.docs_namespace) | ||
406 | 17 | import_selected_external_docs_branches.short_description = \ | ||
407 | 18 | "Import selected branches" | ||
408 | 19 | |||
409 | 20 | |||
410 | 21 | class RevisionAdmin(admin.ModelAdmin): | 13 | class RevisionAdmin(admin.ModelAdmin): |
411 | 22 | list_display = ('date_created', 'user', 'comment') | 14 | list_display = ('date_created', 'user', 'comment') |
412 | 23 | list_display_links = ('date_created', ) | 15 | list_display_links = ('date_created', ) |
413 | @@ -35,13 +27,6 @@ | |||
414 | 35 | admin.site.register(Version, VersionAdmin) | 27 | admin.site.register(Version, VersionAdmin) |
415 | 36 | 28 | ||
416 | 37 | 29 | ||
417 | 38 | class ExternalDocsBranchAdmin(admin.ModelAdmin): | ||
418 | 39 | list_display = ('lp_origin', 'docs_namespace') | ||
419 | 40 | list_filter = ('lp_origin', 'docs_namespace') | ||
420 | 41 | actions = [import_selected_external_docs_branches] | ||
421 | 42 | |||
422 | 43 | admin.site.register(ExternalDocsBranch, ExternalDocsBranchAdmin) | ||
423 | 44 | |||
424 | 45 | class SEOExtensionAdmin(TitleExtensionAdmin): | 30 | class SEOExtensionAdmin(TitleExtensionAdmin): |
425 | 46 | pass | 31 | pass |
426 | 47 | 32 | ||
427 | 48 | 33 | ||
428 | === modified file 'developer_portal/blog/views.py' | |||
429 | --- developer_portal/blog/views.py 2015-12-08 10:25:29 +0000 | |||
430 | +++ developer_portal/blog/views.py 2016-01-19 00:21:38 +0000 | |||
431 | @@ -35,7 +35,9 @@ | |||
432 | 35 | return super(MultiLangEntryIndex, self).get(request, *args, **kwargs) | 35 | return super(MultiLangEntryIndex, self).get(request, *args, **kwargs) |
433 | 36 | 36 | ||
434 | 37 | def get_dated_queryset(self, ordering=None, **lookup): | 37 | def get_dated_queryset(self, ordering=None, **lookup): |
436 | 38 | return super(MultiLangEntryIndex, self).get_dated_queryset(ordering, **lookup).filter(categories__slug=self.language) | 38 | if ordering: |
437 | 39 | return super(MultiLangEntryIndex, self).get_dated_queryset(**lookup).filter(categories__slug=self.language).order_by(ordering) | ||
438 | 40 | return super(MultiLangEntryIndex, self).get_dated_queryset(**lookup).filter(categories__slug=self.language) | ||
439 | 39 | 41 | ||
440 | 40 | class MultiLangEntryYear(MultiLangMixin, EntryYear): | 42 | class MultiLangEntryYear(MultiLangMixin, EntryYear): |
441 | 41 | def get(self, request, *args, **kwargs): | 43 | def get(self, request, *args, **kwargs): |
442 | @@ -43,7 +45,9 @@ | |||
443 | 43 | return super(MultiLangEntryYear, self).get(request, *args, **kwargs) | 45 | return super(MultiLangEntryYear, self).get(request, *args, **kwargs) |
444 | 44 | 46 | ||
445 | 45 | def get_dated_queryset(self, ordering=None, **lookup): | 47 | def get_dated_queryset(self, ordering=None, **lookup): |
447 | 46 | return super(MultiLangEntryYear, self).get_dated_queryset(ordering, **lookup).filter(categories__slug=self.language) | 48 | if ordering: |
448 | 49 | return super(MultiLangEntryYear, self).get_dated_queryset(**lookup).filter(categories__slug=self.language).order_by(ordering) | ||
449 | 50 | return super(MultiLangEntryYear, self).get_dated_queryset(**lookup).filter(categories__slug=self.language) | ||
450 | 47 | 51 | ||
451 | 48 | class MultiLangEntryMonth(MultiLangMixin, EntryMonth): | 52 | class MultiLangEntryMonth(MultiLangMixin, EntryMonth): |
452 | 49 | def get(self, request, *args, **kwargs): | 53 | def get(self, request, *args, **kwargs): |
453 | @@ -51,7 +55,9 @@ | |||
454 | 51 | return super(MultiLangEntryMonth, self).get(request, *args, **kwargs) | 55 | return super(MultiLangEntryMonth, self).get(request, *args, **kwargs) |
455 | 52 | 56 | ||
456 | 53 | def get_dated_queryset(self, ordering=None, **lookup): | 57 | def get_dated_queryset(self, ordering=None, **lookup): |
458 | 54 | return super(MultiLangEntryMonth, self).get_dated_queryset(ordering, **lookup).filter(categories__slug=self.language) | 58 | if ordering: |
459 | 59 | return super(MultiLangEntryMonth, self).get_dated_queryset(**lookup).filter(categories__slug=self.language).order_by(ordering) | ||
460 | 60 | return super(MultiLangEntryMonth, self).get_dated_queryset(**lookup).filter(categories__slug=self.language) | ||
461 | 55 | 61 | ||
462 | 56 | class MultiLangEntryWeek(MultiLangMixin, EntryWeek): | 62 | class MultiLangEntryWeek(MultiLangMixin, EntryWeek): |
463 | 57 | def get(self, request, *args, **kwargs): | 63 | def get(self, request, *args, **kwargs): |
464 | @@ -59,7 +65,9 @@ | |||
465 | 59 | return super(MultiLangEntryWeek, self).get(request, *args, **kwargs) | 65 | return super(MultiLangEntryWeek, self).get(request, *args, **kwargs) |
466 | 60 | 66 | ||
467 | 61 | def get_dated_queryset(self, ordering=None, **lookup): | 67 | def get_dated_queryset(self, ordering=None, **lookup): |
469 | 62 | return super(MultiLangEntryWeek, self).get_dated_queryset(ordering, **lookup).filter(categories__slug=self.language) | 68 | if ordering: |
470 | 69 | return super(MultiLangEntryWeek, self).get_dated_queryset(**lookup).filter(categories__slug=self.language).order_by(ordering) | ||
471 | 70 | return super(MultiLangEntryWeek, self).get_dated_queryset(**lookup).filter(categories__slug=self.language) | ||
472 | 63 | 71 | ||
473 | 64 | class MultiLangEntryDay(MultiLangMixin, EntryDay): | 72 | class MultiLangEntryDay(MultiLangMixin, EntryDay): |
474 | 65 | def get(self, request, *args, **kwargs): | 73 | def get(self, request, *args, **kwargs): |
475 | @@ -67,7 +75,9 @@ | |||
476 | 67 | return super(MultiLangEntryDay, self).get(request, *args, **kwargs) | 75 | return super(MultiLangEntryDay, self).get(request, *args, **kwargs) |
477 | 68 | 76 | ||
478 | 69 | def get_dated_queryset(self, ordering=None, **lookup): | 77 | def get_dated_queryset(self, ordering=None, **lookup): |
480 | 70 | return super(MultiLangEntryDay, self).get_dated_queryset(ordering, **lookup).filter(categories__slug=self.language) | 78 | if ordering: |
481 | 79 | return super(MultiLangEntryDay, self).get_dated_queryset(**lookup).filter(categories__slug=self.language).order_by(ordering) | ||
482 | 80 | return super(MultiLangEntryDay, self).get_dated_queryset(**lookup).filter(categories__slug=self.language) | ||
483 | 71 | 81 | ||
484 | 72 | class MultiLangEntryToday(MultiLangMixin, EntryToday): | 82 | class MultiLangEntryToday(MultiLangMixin, EntryToday): |
485 | 73 | def get(self, request, *args, **kwargs): | 83 | def get(self, request, *args, **kwargs): |
486 | @@ -75,4 +85,6 @@ | |||
487 | 75 | return super(MultiLangEntryToday, self).get(request, *args, **kwargs) | 85 | return super(MultiLangEntryToday, self).get(request, *args, **kwargs) |
488 | 76 | 86 | ||
489 | 77 | def get_dated_queryset(self, ordering=None, **lookup): | 87 | def get_dated_queryset(self, ordering=None, **lookup): |
491 | 78 | return super(MultiLangEntryToday, self).get_dated_queryset(ordering, **lookup).filter(categories__slug=self.language) | 88 | if ordering: |
492 | 89 | return super(MultiLangEntryToday, self).get_dated_queryset(**lookup).filter(categories__slug=self.language).order_by(ordering) | ||
493 | 90 | return super(MultiLangEntryToday, self).get_dated_queryset(**lookup).filter(categories__slug=self.language) | ||
494 | 79 | 91 | ||
495 | === modified file 'developer_portal/management/commands/initdb.py' | |||
496 | --- developer_portal/management/commands/initdb.py 2015-12-08 10:25:29 +0000 | |||
497 | +++ developer_portal/management/commands/initdb.py 2016-01-19 00:21:38 +0000 | |||
498 | @@ -1,17 +1,12 @@ | |||
499 | 1 | #!/usr/bin/python | 1 | #!/usr/bin/python |
500 | 2 | 2 | ||
501 | 3 | from django.core.management.base import BaseCommand | 3 | from django.core.management.base import BaseCommand |
502 | 4 | from optparse import make_option | ||
503 | 5 | |||
504 | 6 | from django.conf import settings | 4 | from django.conf import settings |
505 | 7 | 5 | ||
512 | 8 | import subprocess | 6 | from django.contrib.auth.models import User, Permission |
507 | 9 | import os | ||
508 | 10 | import sys | ||
509 | 11 | |||
510 | 12 | from django.contrib.auth.models import User, Group, Permission | ||
511 | 13 | from django.contrib.contenttypes.models import ContentType | ||
513 | 14 | from cms.models.permissionmodels import PageUserGroup, GlobalPagePermission | 7 | from cms.models.permissionmodels import PageUserGroup, GlobalPagePermission |
514 | 8 | from zinnia.models import Category | ||
515 | 9 | |||
516 | 15 | 10 | ||
517 | 16 | class Command(BaseCommand): | 11 | class Command(BaseCommand): |
518 | 17 | help = "Make sure the Developer Portal database is set up properly." | 12 | help = "Make sure the Developer Portal database is set up properly." |
519 | @@ -20,24 +15,25 @@ | |||
520 | 20 | 15 | ||
521 | 21 | all_perms = Permission.objects.filter() | 16 | all_perms = Permission.objects.filter() |
522 | 22 | 17 | ||
524 | 23 | print "Creating admin user." | 18 | print("Creating admin user.") |
525 | 24 | admin, created = User.objects.get_or_create(username='system') | 19 | admin, created = User.objects.get_or_create(username='system') |
526 | 25 | admin.is_staff = True | 20 | admin.is_staff = True |
527 | 26 | admin.is_superuser = True | 21 | admin.is_superuser = True |
528 | 27 | admin.save() | 22 | admin.save() |
529 | 28 | 23 | ||
530 | 29 | if hasattr(settings, 'ADMIN_GROUP') and settings.ADMIN_GROUP != "": | 24 | if hasattr(settings, 'ADMIN_GROUP') and settings.ADMIN_GROUP != "": |
533 | 30 | print "Configuring "+settings.ADMIN_GROUP+" group." | 25 | print("Configuring {} group.".format(settings.ADMIN_GROUP)) |
534 | 31 | admins, created = PageUserGroup.objects.get_or_create(name=settings.ADMIN_GROUP, defaults={'created_by': admin}) | 26 | admins, created = PageUserGroup.objects.get_or_create( |
535 | 27 | name=settings.ADMIN_GROUP, defaults={'created_by': admin}) | ||
536 | 32 | admins.permissions.add(*list(all_perms)) | 28 | admins.permissions.add(*list(all_perms)) |
537 | 33 | 29 | ||
539 | 34 | print "Configuring global permissions for group." | 30 | print("Configuring global permissions for group.") |
540 | 35 | adminperms, created = GlobalPagePermission.objects.get_or_create( | 31 | adminperms, created = GlobalPagePermission.objects.get_or_create( |
541 | 36 | # who: | 32 | # who: |
543 | 37 | group = admins, | 33 | group=admins, |
544 | 38 | 34 | ||
545 | 39 | # what: | 35 | # what: |
547 | 40 | defaults = { | 36 | defaults={ |
548 | 41 | 'can_change': True, | 37 | 'can_change': True, |
549 | 42 | 'can_add': True, | 38 | 'can_add': True, |
550 | 43 | 'can_delete': True, | 39 | 'can_delete': True, |
551 | @@ -51,18 +47,20 @@ | |||
552 | 51 | adminperms.sites.add(settings.SITE_ID) | 47 | adminperms.sites.add(settings.SITE_ID) |
553 | 52 | 48 | ||
554 | 53 | if hasattr(settings, 'EDITOR_GROUP') and settings.EDITOR_GROUP != "": | 49 | if hasattr(settings, 'EDITOR_GROUP') and settings.EDITOR_GROUP != "": |
558 | 54 | print "Configuring "+settings.EDITOR_GROUP+" group." | 50 | print("Configuring {} group.".format(settings.EDITOR_GROUP)) |
559 | 55 | editors, created = PageUserGroup.objects.get_or_create(name=settings.EDITOR_GROUP, defaults={'created_by': admin}) | 51 | editors, created = PageUserGroup.objects.get_or_create( |
560 | 56 | page_perms = Permission.objects.filter(content_type__app_label='cms', content_type__name='page') | 52 | name=settings.EDITOR_GROUP, defaults={'created_by': admin}) |
561 | 53 | page_perms = Permission.objects.filter( | ||
562 | 54 | content_type__app_label='cms', content_type__model='page') | ||
563 | 57 | editors.permissions.add(*list(page_perms)) | 55 | editors.permissions.add(*list(page_perms)) |
564 | 58 | 56 | ||
566 | 59 | print "Configuring global permissions for group." | 57 | print("Configuring global permissions for group.") |
567 | 60 | editorsperms, created = GlobalPagePermission.objects.get_or_create( | 58 | editorsperms, created = GlobalPagePermission.objects.get_or_create( |
568 | 61 | # who: | 59 | # who: |
570 | 62 | group = editors, | 60 | group=editors, |
571 | 63 | 61 | ||
572 | 64 | # what: | 62 | # what: |
574 | 65 | defaults = { | 63 | defaults={ |
575 | 66 | 'can_change': True, | 64 | 'can_change': True, |
576 | 67 | 'can_add': True, | 65 | 'can_add': True, |
577 | 68 | 'can_delete': True, | 66 | 'can_delete': True, |
578 | @@ -74,3 +72,11 @@ | |||
579 | 74 | } | 72 | } |
580 | 75 | ) | 73 | ) |
581 | 76 | editorsperms.sites.add(settings.SITE_ID) | 74 | editorsperms.sites.add(settings.SITE_ID) |
582 | 75 | |||
583 | 76 | print('Adding zinnia categories for the following: {}.'.format( | ||
584 | 77 | ', '.join([a[0] for a in settings.LANGUAGES]))) | ||
585 | 78 | for lang in settings.LANGUAGES: | ||
586 | 79 | if lang[1] == 'Simplified Chinese': | ||
587 | 80 | Category.objects.get_or_create(title='Chinese', slug=lang[0]) | ||
588 | 81 | else: | ||
589 | 82 | Category.objects.get_or_create(title=lang[1], slug=lang[0]) | ||
590 | 77 | 83 | ||
591 | === modified file 'developer_portal/management/commands/update-template.py' | |||
592 | --- developer_portal/management/commands/update-template.py 2015-07-09 07:23:54 +0000 | |||
593 | +++ developer_portal/management/commands/update-template.py 2016-01-19 00:21:38 +0000 | |||
594 | @@ -1,38 +1,35 @@ | |||
595 | 1 | #!/usr/bin/python | 1 | #!/usr/bin/python |
596 | 2 | 2 | ||
597 | 3 | from django.core.management.base import NoArgsCommand | 3 | from django.core.management.base import NoArgsCommand |
598 | 4 | from django.core.management import call_command | ||
599 | 4 | 5 | ||
600 | 5 | import subprocess | ||
601 | 6 | import os | 6 | import os |
602 | 7 | import sys | ||
603 | 8 | 7 | ||
604 | 9 | from django.conf import settings | 8 | from django.conf import settings |
605 | 10 | 9 | ||
607 | 11 | APP_NAME = "developer_portal" | 10 | APP_NAME = 'developer_portal' |
608 | 12 | 11 | ||
610 | 13 | DUMMY_LOCALE = "xx" | 12 | DUMMY_LOCALE = 'xx' |
611 | 14 | 13 | ||
612 | 15 | 14 | ||
613 | 16 | def update_template(): | 15 | def update_template(): |
620 | 17 | pwd = os.getcwd() | 16 | call_command( |
621 | 18 | os.chdir(settings.PROJECT_PATH) | 17 | 'makemessages', |
622 | 19 | subprocess.call([sys.executable, "manage.py", "makemessages", | 18 | '--keep-pot', '-i', 'env/*', '-i', 'urls.py', |
623 | 20 | "--keep-pot", "--all", "-i", "env/*", "-i", "urls.py", | 19 | '-l', DUMMY_LOCALE) |
624 | 21 | "-l", DUMMY_LOCALE]) | 20 | project_locale_path = os.path.join(settings.PROJECT_PATH, 'locale') |
619 | 22 | project_locale_path = os.path.join(settings.PROJECT_PATH, "locale") | ||
625 | 23 | os.rename(os.path.join(project_locale_path, | 21 | os.rename(os.path.join(project_locale_path, |
628 | 24 | "%s/LC_MESSAGES/django.po" % DUMMY_LOCALE), | 22 | '%s/LC_MESSAGES/django.po' % DUMMY_LOCALE), |
629 | 25 | os.path.join(project_locale_path, "%s.pot" % APP_NAME)) | 23 | os.path.join(project_locale_path, '%s.pot' % APP_NAME)) |
630 | 26 | os.removedirs(os.path.join(project_locale_path, | 24 | os.removedirs(os.path.join(project_locale_path, |
633 | 27 | "%s/LC_MESSAGES" % DUMMY_LOCALE)) | 25 | '%s/LC_MESSAGES' % DUMMY_LOCALE)) |
634 | 28 | old_pot_fn = os.path.join(project_locale_path, "django.pot") | 26 | old_pot_fn = os.path.join(project_locale_path, 'django.pot') |
635 | 29 | if os.path.exists(old_pot_fn): | 27 | if os.path.exists(old_pot_fn): |
636 | 30 | os.remove(old_pot_fn) | 28 | os.remove(old_pot_fn) |
637 | 31 | os.chdir(pwd) | ||
638 | 32 | 29 | ||
639 | 33 | 30 | ||
640 | 34 | class Command(NoArgsCommand): | 31 | class Command(NoArgsCommand): |
642 | 35 | help = "Update translations template." | 32 | help = 'Update translations template.' |
643 | 36 | 33 | ||
644 | 37 | def handle_noargs(self, **options): | 34 | def handle_noargs(self, **options): |
645 | 38 | update_template() | 35 | update_template() |
646 | 39 | 36 | ||
647 | === added file 'developer_portal/migrations/0001_initial.py' | |||
648 | --- developer_portal/migrations/0001_initial.py 1970-01-01 00:00:00 +0000 | |||
649 | +++ developer_portal/migrations/0001_initial.py 2016-01-19 00:21:38 +0000 | |||
650 | @@ -0,0 +1,36 @@ | |||
651 | 1 | # -*- coding: utf-8 -*- | ||
652 | 2 | from __future__ import unicode_literals | ||
653 | 3 | |||
654 | 4 | from django.db import migrations, models | ||
655 | 5 | |||
656 | 6 | |||
657 | 7 | class Migration(migrations.Migration): | ||
658 | 8 | |||
659 | 9 | dependencies = [ | ||
660 | 10 | ] | ||
661 | 11 | |||
662 | 12 | operations = [ | ||
663 | 13 | migrations.CreateModel( | ||
664 | 14 | name='RawHtml', | ||
665 | 15 | fields=[ | ||
666 | 16 | ('cmsplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin')), | ||
667 | 17 | ('body', models.TextField(verbose_name='body')), | ||
668 | 18 | ], | ||
669 | 19 | options={ | ||
670 | 20 | 'abstract': False, | ||
671 | 21 | }, | ||
672 | 22 | bases=('cms.cmsplugin',), | ||
673 | 23 | ), | ||
674 | 24 | migrations.CreateModel( | ||
675 | 25 | name='SEOExtension', | ||
676 | 26 | fields=[ | ||
677 | 27 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | ||
678 | 28 | ('keywords', models.CharField(max_length=256)), | ||
679 | 29 | ('extended_object', models.OneToOneField(editable=False, to='cms.Title')), | ||
680 | 30 | ('public_extension', models.OneToOneField(related_name='draft_extension', null=True, editable=False, to='developer_portal.SEOExtension')), | ||
681 | 31 | ], | ||
682 | 32 | options={ | ||
683 | 33 | 'abstract': False, | ||
684 | 34 | }, | ||
685 | 35 | ), | ||
686 | 36 | ] | ||
687 | 0 | 37 | ||
688 | === removed file 'developer_portal/migrations/0001_initial.py' | |||
689 | --- developer_portal/migrations/0001_initial.py 2015-12-08 10:25:29 +0000 | |||
690 | +++ developer_portal/migrations/0001_initial.py 1970-01-01 00:00:00 +0000 | |||
691 | @@ -1,20 +0,0 @@ | |||
692 | 1 | # -*- coding: utf-8 -*- | ||
693 | 2 | from south.utils import datetime_utils as datetime | ||
694 | 3 | from south.db import db | ||
695 | 4 | from south.v2 import SchemaMigration | ||
696 | 5 | from django.db import models | ||
697 | 6 | |||
698 | 7 | |||
699 | 8 | class Migration(SchemaMigration): | ||
700 | 9 | |||
701 | 10 | def forwards(self, orm): | ||
702 | 11 | pass | ||
703 | 12 | |||
704 | 13 | def backwards(self, orm): | ||
705 | 14 | pass | ||
706 | 15 | |||
707 | 16 | models = { | ||
708 | 17 | |||
709 | 18 | } | ||
710 | 19 | |||
711 | 20 | complete_apps = ['developer_portal'] | ||
712 | 21 | \ No newline at end of file | 0 | \ No newline at end of file |
713 | 22 | 1 | ||
714 | === removed file 'developer_portal/migrations/0002_add_rawhtml_plugin.py' | |||
715 | --- developer_portal/migrations/0002_add_rawhtml_plugin.py 2015-12-08 10:25:29 +0000 | |||
716 | +++ developer_portal/migrations/0002_add_rawhtml_plugin.py 1970-01-01 00:00:00 +0000 | |||
717 | @@ -1,53 +0,0 @@ | |||
718 | 1 | # -*- coding: utf-8 -*- | ||
719 | 2 | from south.utils import datetime_utils as datetime | ||
720 | 3 | from south.db import db | ||
721 | 4 | from south.v2 import SchemaMigration | ||
722 | 5 | from django.db import models | ||
723 | 6 | |||
724 | 7 | |||
725 | 8 | class Migration(SchemaMigration): | ||
726 | 9 | |||
727 | 10 | def forwards(self, orm): | ||
728 | 11 | # Adding model 'RawHtml' | ||
729 | 12 | db.create_table(u'developer_portal_rawhtml', ( | ||
730 | 13 | (u'cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)), | ||
731 | 14 | ('body', self.gf('django.db.models.fields.TextField')()), | ||
732 | 15 | )) | ||
733 | 16 | db.send_create_signal(u'developer_portal', ['RawHtml']) | ||
734 | 17 | |||
735 | 18 | |||
736 | 19 | def backwards(self, orm): | ||
737 | 20 | # Deleting model 'RawHtml' | ||
738 | 21 | db.delete_table(u'developer_portal_rawhtml') | ||
739 | 22 | |||
740 | 23 | |||
741 | 24 | models = { | ||
742 | 25 | 'cms.cmsplugin': { | ||
743 | 26 | 'Meta': {'object_name': 'CMSPlugin'}, | ||
744 | 27 | 'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), | ||
745 | 28 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
746 | 29 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
747 | 30 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), | ||
748 | 31 | 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
749 | 32 | 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
750 | 33 | 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}), | ||
751 | 34 | 'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}), | ||
752 | 35 | 'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), | ||
753 | 36 | 'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}), | ||
754 | 37 | 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
755 | 38 | 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) | ||
756 | 39 | }, | ||
757 | 40 | 'cms.placeholder': { | ||
758 | 41 | 'Meta': {'object_name': 'Placeholder'}, | ||
759 | 42 | 'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), | ||
760 | 43 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
761 | 44 | 'slot': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) | ||
762 | 45 | }, | ||
763 | 46 | u'developer_portal.rawhtml': { | ||
764 | 47 | 'Meta': {'object_name': 'RawHtml'}, | ||
765 | 48 | 'body': ('django.db.models.fields.TextField', [], {}), | ||
766 | 49 | u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}) | ||
767 | 50 | } | ||
768 | 51 | } | ||
769 | 52 | |||
770 | 53 | complete_apps = ['developer_portal'] | ||
771 | 54 | \ No newline at end of file | 0 | \ No newline at end of file |
772 | 55 | 1 | ||
773 | === removed file 'developer_portal/migrations/0003_add_external_docs_branches.py' | |||
774 | --- developer_portal/migrations/0003_add_external_docs_branches.py 2015-12-08 10:25:29 +0000 | |||
775 | +++ developer_portal/migrations/0003_add_external_docs_branches.py 1970-01-01 00:00:00 +0000 | |||
776 | @@ -1,62 +0,0 @@ | |||
777 | 1 | # -*- coding: utf-8 -*- | ||
778 | 2 | from south.utils import datetime_utils as datetime | ||
779 | 3 | from south.db import db | ||
780 | 4 | from south.v2 import SchemaMigration | ||
781 | 5 | from django.db import models | ||
782 | 6 | |||
783 | 7 | |||
784 | 8 | class Migration(SchemaMigration): | ||
785 | 9 | |||
786 | 10 | def forwards(self, orm): | ||
787 | 11 | # Adding model 'ExternalDocsBranch' | ||
788 | 12 | db.create_table(u'developer_portal_externaldocsbranch', ( | ||
789 | 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), | ||
790 | 14 | ('lp_origin', self.gf('django.db.models.fields.CharField')(max_length=200)), | ||
791 | 15 | ('docs_namespace', self.gf('django.db.models.fields.CharField')(max_length=120)), | ||
792 | 16 | ('index_doc', self.gf('django.db.models.fields.CharField')(max_length=120, blank=True)), | ||
793 | 17 | )) | ||
794 | 18 | db.send_create_signal(u'developer_portal', ['ExternalDocsBranch']) | ||
795 | 19 | |||
796 | 20 | |||
797 | 21 | def backwards(self, orm): | ||
798 | 22 | # Deleting model 'ExternalDocsBranch' | ||
799 | 23 | db.delete_table(u'developer_portal_externaldocsbranch') | ||
800 | 24 | |||
801 | 25 | |||
802 | 26 | models = { | ||
803 | 27 | 'cms.cmsplugin': { | ||
804 | 28 | 'Meta': {'object_name': 'CMSPlugin'}, | ||
805 | 29 | 'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), | ||
806 | 30 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
807 | 31 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
808 | 32 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), | ||
809 | 33 | 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
810 | 34 | 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
811 | 35 | 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}), | ||
812 | 36 | 'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}), | ||
813 | 37 | 'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), | ||
814 | 38 | 'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}), | ||
815 | 39 | 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
816 | 40 | 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) | ||
817 | 41 | }, | ||
818 | 42 | 'cms.placeholder': { | ||
819 | 43 | 'Meta': {'object_name': 'Placeholder'}, | ||
820 | 44 | 'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), | ||
821 | 45 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
822 | 46 | 'slot': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) | ||
823 | 47 | }, | ||
824 | 48 | u'developer_portal.externaldocsbranch': { | ||
825 | 49 | 'Meta': {'object_name': 'ExternalDocsBranch'}, | ||
826 | 50 | 'docs_namespace': ('django.db.models.fields.CharField', [], {'max_length': '120'}), | ||
827 | 51 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
828 | 52 | 'index_doc': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}), | ||
829 | 53 | 'lp_origin': ('django.db.models.fields.CharField', [], {'max_length': '200'}) | ||
830 | 54 | }, | ||
831 | 55 | u'developer_portal.rawhtml': { | ||
832 | 56 | 'Meta': {'object_name': 'RawHtml'}, | ||
833 | 57 | 'body': ('django.db.models.fields.TextField', [], {}), | ||
834 | 58 | u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}) | ||
835 | 59 | } | ||
836 | 60 | } | ||
837 | 61 | |||
838 | 62 | complete_apps = ['developer_portal'] | ||
839 | 63 | \ No newline at end of file | 0 | \ No newline at end of file |
840 | 64 | 1 | ||
841 | === removed file 'developer_portal/migrations/0004_auto__add_seoextension.py' | |||
842 | --- developer_portal/migrations/0004_auto__add_seoextension.py 2015-12-08 10:25:29 +0000 | |||
843 | +++ developer_portal/migrations/0004_auto__add_seoextension.py 1970-01-01 00:00:00 +0000 | |||
844 | @@ -1,126 +0,0 @@ | |||
845 | 1 | # -*- coding: utf-8 -*- | ||
846 | 2 | from south.utils import datetime_utils as datetime | ||
847 | 3 | from south.db import db | ||
848 | 4 | from south.v2 import SchemaMigration | ||
849 | 5 | from django.db import models | ||
850 | 6 | |||
851 | 7 | |||
852 | 8 | class Migration(SchemaMigration): | ||
853 | 9 | |||
854 | 10 | def forwards(self, orm): | ||
855 | 11 | # Adding model 'SEOExtension' | ||
856 | 12 | db.create_table(u'developer_portal_seoextension', ( | ||
857 | 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), | ||
858 | 14 | ('public_extension', self.gf('django.db.models.fields.related.OneToOneField')(related_name='draft_extension', unique=True, null=True, to=orm['developer_portal.SEOExtension'])), | ||
859 | 15 | ('extended_object', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.Title'], unique=True)), | ||
860 | 16 | ('keywords', self.gf('django.db.models.fields.CharField')(max_length=256)), | ||
861 | 17 | )) | ||
862 | 18 | db.send_create_signal(u'developer_portal', ['SEOExtension']) | ||
863 | 19 | |||
864 | 20 | |||
865 | 21 | def backwards(self, orm): | ||
866 | 22 | # Deleting model 'SEOExtension' | ||
867 | 23 | db.delete_table(u'developer_portal_seoextension') | ||
868 | 24 | |||
869 | 25 | |||
870 | 26 | models = { | ||
871 | 27 | 'cms.cmsplugin': { | ||
872 | 28 | 'Meta': {'object_name': 'CMSPlugin'}, | ||
873 | 29 | 'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), | ||
874 | 30 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
875 | 31 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
876 | 32 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), | ||
877 | 33 | 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
878 | 34 | 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
879 | 35 | 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}), | ||
880 | 36 | 'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}), | ||
881 | 37 | 'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), | ||
882 | 38 | 'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}), | ||
883 | 39 | 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
884 | 40 | 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) | ||
885 | 41 | }, | ||
886 | 42 | 'cms.page': { | ||
887 | 43 | 'Meta': {'ordering': "('tree_id', 'lft')", 'unique_together': "(('publisher_is_draft', 'application_namespace'), ('reverse_id', 'site', 'publisher_is_draft'))", 'object_name': 'Page'}, | ||
888 | 44 | 'application_namespace': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), | ||
889 | 45 | 'application_urls': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '200', 'null': 'True', 'blank': 'True'}), | ||
890 | 46 | 'changed_by': ('django.db.models.fields.CharField', [], {'max_length': '70'}), | ||
891 | 47 | 'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), | ||
892 | 48 | 'created_by': ('django.db.models.fields.CharField', [], {'max_length': '70'}), | ||
893 | 49 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), | ||
894 | 50 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
895 | 51 | 'in_navigation': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), | ||
896 | 52 | 'is_home': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), | ||
897 | 53 | 'languages': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), | ||
898 | 54 | 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
899 | 55 | 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
900 | 56 | 'limit_visibility_in_menu': ('django.db.models.fields.SmallIntegerField', [], {'default': 'None', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), | ||
901 | 57 | 'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
902 | 58 | 'navigation_extenders': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), | ||
903 | 59 | 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['cms.Page']"}), | ||
904 | 60 | 'placeholders': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cms.Placeholder']", 'symmetrical': 'False'}), | ||
905 | 61 | 'publication_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), | ||
906 | 62 | 'publication_end_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), | ||
907 | 63 | 'publisher_is_draft': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), | ||
908 | 64 | 'publisher_public': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'publisher_draft'", 'unique': 'True', 'null': 'True', 'to': "orm['cms.Page']"}), | ||
909 | 65 | 'reverse_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '40', 'null': 'True', 'blank': 'True'}), | ||
910 | 66 | 'revision_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), | ||
911 | 67 | 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
912 | 68 | 'site': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'djangocms_pages'", 'to': u"orm['sites.Site']"}), | ||
913 | 69 | 'soft_root': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), | ||
914 | 70 | 'template': ('django.db.models.fields.CharField', [], {'default': "'INHERIT'", 'max_length': '100'}), | ||
915 | 71 | 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), | ||
916 | 72 | 'xframe_options': ('django.db.models.fields.IntegerField', [], {'default': '0'}) | ||
917 | 73 | }, | ||
918 | 74 | 'cms.placeholder': { | ||
919 | 75 | 'Meta': {'object_name': 'Placeholder'}, | ||
920 | 76 | 'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), | ||
921 | 77 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
922 | 78 | 'slot': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) | ||
923 | 79 | }, | ||
924 | 80 | 'cms.title': { | ||
925 | 81 | 'Meta': {'unique_together': "(('language', 'page'),)", 'object_name': 'Title'}, | ||
926 | 82 | 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
927 | 83 | 'has_url_overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), | ||
928 | 84 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
929 | 85 | 'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), | ||
930 | 86 | 'menu_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), | ||
931 | 87 | 'meta_description': ('django.db.models.fields.TextField', [], {'max_length': '155', 'null': 'True', 'blank': 'True'}), | ||
932 | 88 | 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'title_set'", 'to': "orm['cms.Page']"}), | ||
933 | 89 | 'page_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), | ||
934 | 90 | 'path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), | ||
935 | 91 | 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
936 | 92 | 'publisher_is_draft': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), | ||
937 | 93 | 'publisher_public': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'publisher_draft'", 'unique': 'True', 'null': 'True', 'to': "orm['cms.Title']"}), | ||
938 | 94 | 'publisher_state': ('django.db.models.fields.SmallIntegerField', [], {'default': '0', 'db_index': 'True'}), | ||
939 | 95 | 'redirect': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), | ||
940 | 96 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}), | ||
941 | 97 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) | ||
942 | 98 | }, | ||
943 | 99 | u'developer_portal.externaldocsbranch': { | ||
944 | 100 | 'Meta': {'object_name': 'ExternalDocsBranch'}, | ||
945 | 101 | 'docs_namespace': ('django.db.models.fields.CharField', [], {'max_length': '120'}), | ||
946 | 102 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
947 | 103 | 'index_doc': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}), | ||
948 | 104 | 'lp_origin': ('django.db.models.fields.CharField', [], {'max_length': '200'}) | ||
949 | 105 | }, | ||
950 | 106 | u'developer_portal.rawhtml': { | ||
951 | 107 | 'Meta': {'object_name': 'RawHtml'}, | ||
952 | 108 | 'body': ('django.db.models.fields.TextField', [], {}), | ||
953 | 109 | u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}) | ||
954 | 110 | }, | ||
955 | 111 | u'developer_portal.seoextension': { | ||
956 | 112 | 'Meta': {'object_name': 'SEOExtension'}, | ||
957 | 113 | 'extended_object': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.Title']", 'unique': 'True'}), | ||
958 | 114 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
959 | 115 | 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '256'}), | ||
960 | 116 | 'public_extension': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'draft_extension'", 'unique': 'True', 'null': 'True', 'to': u"orm['developer_portal.SEOExtension']"}) | ||
961 | 117 | }, | ||
962 | 118 | u'sites.site': { | ||
963 | 119 | 'Meta': {'ordering': "(u'domain',)", 'object_name': 'Site', 'db_table': "u'django_site'"}, | ||
964 | 120 | 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
965 | 121 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
966 | 122 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) | ||
967 | 123 | } | ||
968 | 124 | } | ||
969 | 125 | |||
970 | 126 | complete_apps = ['developer_portal'] | ||
971 | 127 | \ No newline at end of file | 0 | \ No newline at end of file |
972 | 128 | 1 | ||
973 | === modified file 'developer_portal/models.py' | |||
974 | --- developer_portal/models.py 2015-11-02 16:47:26 +0000 | |||
975 | +++ developer_portal/models.py 2016-01-19 00:21:38 +0000 | |||
976 | @@ -1,7 +1,5 @@ | |||
977 | 1 | from django.db import models | 1 | from django.db import models |
978 | 2 | from django.utils.translation import ugettext_lazy as _ | ||
979 | 3 | 2 | ||
980 | 4 | from cms.models import CMSPlugin | ||
981 | 5 | from cms.extensions import TitleExtension | 3 | from cms.extensions import TitleExtension |
982 | 6 | from cms.extensions.extension_pool import extension_pool | 4 | from cms.extensions.extension_pool import extension_pool |
983 | 7 | from djangocms_text_ckeditor.html import extract_images | 5 | from djangocms_text_ckeditor.html import extract_images |
984 | @@ -20,25 +18,6 @@ | |||
985 | 20 | AbstractText.save(self, *args, **kwargs) | 18 | AbstractText.save(self, *args, **kwargs) |
986 | 21 | 19 | ||
987 | 22 | 20 | ||
988 | 23 | class ExternalDocsBranch(models.Model): | ||
989 | 24 | # We originally assumed that branches would also live in LP, | ||
990 | 25 | # well, we were wrong, but let's keep the name around. It's | ||
991 | 26 | # no use having a schema/data migration just for this. | ||
992 | 27 | lp_origin = models.CharField( | ||
993 | 28 | max_length=200, | ||
994 | 29 | help_text=_('External branch location, ie: lp:snappy/15.04 or ' | ||
995 | 30 | 'https://github.com/ubuntu-core/snappy.git')) | ||
996 | 31 | docs_namespace = models.CharField( | ||
997 | 32 | max_length=120, | ||
998 | 33 | help_text=_('Path alias we want to use for the docs, ' | ||
999 | 34 | 'ie "snappy/guides/15.04" or ' | ||
1000 | 35 | '"snappy/guides/latest", etc.')) | ||
1001 | 36 | index_doc = models.CharField( | ||
1002 | 37 | max_length=120, | ||
1003 | 38 | help_text=_('File name of doc to be used as index document, ' | ||
1004 | 39 | 'ie "intro.md"'), | ||
1005 | 40 | blank=True) | ||
1006 | 41 | |||
1007 | 42 | class SEOExtension(TitleExtension): | 21 | class SEOExtension(TitleExtension): |
1008 | 43 | keywords = models.CharField(max_length=256) | 22 | keywords = models.CharField(max_length=256) |
1009 | 44 | 23 | ||
1010 | 45 | 24 | ||
1011 | === modified file 'developer_portal/settings.py' | |||
1012 | --- developer_portal/settings.py 2015-12-08 10:25:29 +0000 | |||
1013 | +++ developer_portal/settings.py 2016-01-19 00:21:38 +0000 | |||
1014 | @@ -30,8 +30,6 @@ | |||
1015 | 30 | # SECURITY WARNING: don't run with debug turned on in production! | 30 | # SECURITY WARNING: don't run with debug turned on in production! |
1016 | 31 | DEBUG = True | 31 | DEBUG = True |
1017 | 32 | 32 | ||
1018 | 33 | TEMPLATE_DEBUG = True | ||
1019 | 34 | |||
1020 | 35 | ALLOWED_HOSTS = ['127.0.0.1', 'developer.ubuntu.com'] | 33 | ALLOWED_HOSTS = ['127.0.0.1', 'developer.ubuntu.com'] |
1021 | 36 | 34 | ||
1022 | 37 | 35 | ||
1023 | @@ -49,14 +47,13 @@ | |||
1024 | 49 | # Allow login from Ubuntu SSO | 47 | # Allow login from Ubuntu SSO |
1025 | 50 | 'django_openid_auth', | 48 | 'django_openid_auth', |
1026 | 51 | 49 | ||
1027 | 52 | 'mptt', #utilities for implementing a modified pre-order traversal tree | ||
1028 | 53 | 'menus', #helper for model independent hierarchical website navigation | 50 | 'menus', #helper for model independent hierarchical website navigation |
1029 | 54 | 'south', #intelligent schema and data migrations | ||
1030 | 55 | 'sekizai', #for javascript and css management | 51 | 'sekizai', #for javascript and css management |
1031 | 56 | 'reversion', #content versioning | 52 | 'reversion', #content versioning |
1032 | 57 | 'django_pygments', | 53 | 'django_pygments', |
1033 | 58 | 'django_comments', | 54 | 'django_comments', |
1034 | 59 | 'tagging', | 55 | 'tagging', |
1035 | 56 | 'template_debug', | ||
1036 | 60 | 57 | ||
1037 | 61 | 'ckeditor', | 58 | 'ckeditor', |
1038 | 62 | 'djangocms_text_ckeditor', | 59 | 'djangocms_text_ckeditor', |
1039 | @@ -66,6 +63,7 @@ | |||
1040 | 66 | 'djangocms_picture', | 63 | 'djangocms_picture', |
1041 | 67 | 'djangocms_video', | 64 | 'djangocms_video', |
1042 | 68 | 'djangocms_snippet', | 65 | 'djangocms_snippet', |
1043 | 66 | 'treebeard', | ||
1044 | 69 | 67 | ||
1045 | 70 | 'cmsplugin_zinnia', | 68 | 'cmsplugin_zinnia', |
1046 | 71 | 'zinnia', | 69 | 'zinnia', |
1047 | @@ -78,6 +76,8 @@ | |||
1048 | 78 | 'store_data', | 76 | 'store_data', |
1049 | 79 | 77 | ||
1050 | 80 | 'api_docs', | 78 | 'api_docs', |
1051 | 79 | |||
1052 | 80 | 'md_importer', | ||
1053 | 81 | ] | 81 | ] |
1054 | 82 | 82 | ||
1055 | 83 | MIDDLEWARE_CLASSES = ( | 83 | MIDDLEWARE_CLASSES = ( |
1056 | @@ -107,24 +107,29 @@ | |||
1057 | 107 | #CACHE_MIDDLEWARE_SECONDS = 3600 | 107 | #CACHE_MIDDLEWARE_SECONDS = 3600 |
1058 | 108 | #CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True | 108 | #CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True |
1059 | 109 | 109 | ||
1078 | 110 | TEMPLATE_CONTEXT_PROCESSORS = ( | 110 | TEMPLATES = [ |
1079 | 111 | 'django.contrib.auth.context_processors.auth', | 111 | { |
1080 | 112 | 'django.core.context_processors.i18n', | 112 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', |
1081 | 113 | 'django.core.context_processors.request', | 113 | 'DIRS': [ |
1082 | 114 | 'django.core.context_processors.media', | 114 | os.path.join(PROJECT_PATH, "templates"), |
1083 | 115 | 'django.core.context_processors.static', | 115 | ], |
1084 | 116 | 116 | 'APP_DIRS': True, | |
1085 | 117 | 'sekizai.context_processors.sekizai', | 117 | 'OPTIONS': { |
1086 | 118 | 'cms.context_processors.cms_settings', | 118 | 'context_processors': [ |
1087 | 119 | 'django.contrib.messages.context_processors.messages', | 119 | 'django.core.context_processors.request', |
1088 | 120 | ) | 120 | 'django.contrib.auth.context_processors.auth', |
1089 | 121 | 121 | 'django.core.context_processors.i18n', | |
1090 | 122 | TEMPLATE_DIRS = ( | 122 | 'django.core.context_processors.media', |
1091 | 123 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". | 123 | 'django.core.context_processors.static', |
1092 | 124 | # Always use forward slashes, even on Windows. | 124 | |
1093 | 125 | # Don't forget to use absolute paths, not relative paths. | 125 | 'sekizai.context_processors.sekizai', |
1094 | 126 | os.path.join(PROJECT_PATH, "templates"), | 126 | 'cms.context_processors.cms_settings', |
1095 | 127 | ) | 127 | 'django.contrib.messages.context_processors.messages', |
1096 | 128 | ] | ||
1097 | 129 | } | ||
1098 | 130 | } | ||
1099 | 131 | ] | ||
1100 | 132 | |||
1101 | 128 | 133 | ||
1102 | 129 | ROOT_URLCONF = 'developer_portal.urls' | 134 | ROOT_URLCONF = 'developer_portal.urls' |
1103 | 130 | 135 | ||
1104 | @@ -318,6 +323,23 @@ | |||
1105 | 318 | #'PAGINATE_BY': 10, | 323 | #'PAGINATE_BY': 10, |
1106 | 319 | } | 324 | } |
1107 | 320 | 325 | ||
1108 | 326 | MIGRATION_MODULES = { | ||
1109 | 327 | 'cms': 'cms.migrations', | ||
1110 | 328 | 'cmsplugin_zinnia': 'cmsplugin_zinnia.migrations', | ||
1111 | 329 | 'djangocms_link': 'djangocms_link.migrations', | ||
1112 | 330 | 'djangocms_picture': 'djangocms_picture.migrations', | ||
1113 | 331 | 'djangocms_snippet': 'djangocms_snippet.migrations', | ||
1114 | 332 | 'djangocms_text_ckeditor': 'djangocms_text_ckeditor.migrations', | ||
1115 | 333 | 'djangocms_video': 'djangocms_video.migrations', | ||
1116 | 334 | 'django_comments': 'django_comments.migrations', | ||
1117 | 335 | 'menus': 'menus.migrations', | ||
1118 | 336 | 'rest_framework.authtoken': 'rest_framework.authtoken.migrations', | ||
1119 | 337 | 'reversion': 'reversion.migrations', | ||
1120 | 338 | 'tagging': 'tagging.migrations', | ||
1121 | 339 | 'taggit': 'taggit.migrations', | ||
1122 | 340 | 'zinnia': 'zinnia.migrations', | ||
1123 | 341 | } | ||
1124 | 342 | |||
1125 | 321 | LOGGING = { | 343 | LOGGING = { |
1126 | 322 | 'version': 1, | 344 | 'version': 1, |
1127 | 323 | 'disable_existing_loggers': False, | 345 | 'disable_existing_loggers': False, |
1128 | 324 | 346 | ||
1129 | === modified file 'developer_portal/urls.py' | |||
1130 | --- developer_portal/urls.py 2015-12-08 10:25:29 +0000 | |||
1131 | +++ developer_portal/urls.py 2016-01-19 00:21:38 +0000 | |||
1132 | @@ -32,7 +32,7 @@ | |||
1133 | 32 | 32 | ||
1134 | 33 | urlpatterns += i18n_patterns('', | 33 | urlpatterns += i18n_patterns('', |
1135 | 34 | url(r'^search/', 'developer_portal.views.search', name='search'), | 34 | url(r'^search/', 'developer_portal.views.search', name='search'), |
1137 | 35 | url(r'^ckeditor/', include('ckeditor.urls')), | 35 | url(r'^ckeditor/', include('ckeditor_uploader.urls')), |
1138 | 36 | url(r'^', include('cms.urls')), | 36 | url(r'^', include('cms.urls')), |
1139 | 37 | ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) | 37 | ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) |
1140 | 38 | 38 | ||
1141 | 39 | 39 | ||
1142 | === modified file 'locale/developer_portal.pot' | |||
1143 | --- locale/developer_portal.pot 2015-12-14 13:22:16 +0000 | |||
1144 | +++ locale/developer_portal.pot 2016-01-19 00:21:38 +0000 | |||
1145 | @@ -8,7 +8,7 @@ | |||
1146 | 8 | msgstr "" | 8 | msgstr "" |
1147 | 9 | "Project-Id-Version: PACKAGE VERSION\n" | 9 | "Project-Id-Version: PACKAGE VERSION\n" |
1148 | 10 | "Report-Msgid-Bugs-To: \n" | 10 | "Report-Msgid-Bugs-To: \n" |
1150 | 11 | "POT-Creation-Date: 2015-12-14 13:22+0000\n" | 11 | "POT-Creation-Date: 2016-01-19 00:07+0000\n" |
1151 | 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
1152 | 13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | 13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
1153 | 14 | "Language-Team: LANGUAGE <LL@li.org>\n" | 14 | "Language-Team: LANGUAGE <LL@li.org>\n" |
1154 | @@ -21,44 +21,28 @@ | |||
1155 | 21 | msgid "Raw HTML" | 21 | msgid "Raw HTML" |
1156 | 22 | msgstr "" | 22 | msgstr "" |
1157 | 23 | 23 | ||
1175 | 24 | #: developer_portal/models.py:29 | 24 | #: developer_portal/settings.py:194 developer_portal/settings.py:202 |
1159 | 25 | msgid "" | ||
1160 | 26 | "External branch location, ie: lp:snappy/15.04 or https://github.com/ubuntu-" | ||
1161 | 27 | "core/snappy.git" | ||
1162 | 28 | msgstr "" | ||
1163 | 29 | |||
1164 | 30 | #: developer_portal/models.py:33 | ||
1165 | 31 | msgid "" | ||
1166 | 32 | "Path alias we want to use for the docs, ie \"snappy/guides/15.04\" or " | ||
1167 | 33 | "\"snappy/guides/latest\", etc." | ||
1168 | 34 | msgstr "" | ||
1169 | 35 | |||
1170 | 36 | #: developer_portal/models.py:38 | ||
1171 | 37 | msgid "File name of doc to be used as index document, ie \"intro.md\"" | ||
1172 | 38 | msgstr "" | ||
1173 | 39 | |||
1174 | 40 | #: developer_portal/settings.py:189 developer_portal/settings.py:197 | ||
1176 | 41 | #: templates/translations_dashboard.html:12 | 25 | #: templates/translations_dashboard.html:12 |
1177 | 42 | msgid "English" | 26 | msgid "English" |
1178 | 43 | msgstr "" | 27 | msgstr "" |
1179 | 44 | 28 | ||
1181 | 45 | #: developer_portal/settings.py:190 developer_portal/settings.py:204 | 29 | #: developer_portal/settings.py:195 developer_portal/settings.py:209 |
1182 | 46 | #: templates/translations_dashboard.html:13 | 30 | #: templates/translations_dashboard.html:13 |
1183 | 47 | msgid "Simplified Chinese" | 31 | msgid "Simplified Chinese" |
1184 | 48 | msgstr "" | 32 | msgstr "" |
1185 | 49 | 33 | ||
1187 | 50 | #: developer_portal/settings.py:212 | 34 | #: developer_portal/settings.py:217 |
1188 | 51 | msgid "Spanish" | 35 | msgid "Spanish" |
1189 | 52 | msgstr "" | 36 | msgstr "" |
1190 | 53 | 37 | ||
1192 | 54 | #: developer_portal/settings.py:252 | 38 | #: developer_portal/settings.py:257 |
1193 | 55 | msgid "Page content" | 39 | msgid "Page content" |
1194 | 56 | msgstr "" | 40 | msgstr "" |
1195 | 57 | 41 | ||
1196 | 58 | #. Translators: this is the default text that will be shown | 42 | #. Translators: this is the default text that will be shown |
1197 | 59 | #. to editors when editing a page. You can use some HTML, | 43 | #. to editors when editing a page. You can use some HTML, |
1198 | 60 | #. but don't go wild :) | 44 | #. but don't go wild :) |
1200 | 61 | #: developer_portal/settings.py:260 | 45 | #: developer_portal/settings.py:265 |
1201 | 62 | msgid "<p>Add content here...</p>" | 46 | msgid "<p>Add content here...</p>" |
1202 | 63 | msgstr "" | 47 | msgstr "" |
1203 | 64 | 48 | ||
1204 | @@ -68,6 +52,40 @@ | |||
1205 | 68 | "profile</a> to use the developer site" | 52 | "profile</a> to use the developer site" |
1206 | 69 | msgstr "" | 53 | msgstr "" |
1207 | 70 | 54 | ||
1208 | 55 | #: md_importer/models.py:10 | ||
1209 | 56 | msgid "" | ||
1210 | 57 | "External branch location, ie: lp:snappy/15.04 or https://github.com/ubuntu-" | ||
1211 | 58 | "core/snappy.git" | ||
1212 | 59 | msgstr "" | ||
1213 | 60 | |||
1214 | 61 | #: md_importer/models.py:14 | ||
1215 | 62 | msgid "For use with git branches, ie: \"master\" or \"15.04\" or \"1.x\"." | ||
1216 | 63 | msgstr "" | ||
1217 | 64 | |||
1218 | 65 | #: md_importer/models.py:19 | ||
1219 | 66 | msgid "Command to run after checkout of the branch." | ||
1220 | 67 | msgstr "" | ||
1221 | 68 | |||
1222 | 69 | #: md_importer/models.py:37 | ||
1223 | 70 | msgid "" | ||
1224 | 71 | "File or directory to import from the branch. Ie: \"docs/intro.md\" (file) or " | ||
1225 | 72 | "\"docs\" (complete directory), etc." | ||
1226 | 73 | msgstr "" | ||
1227 | 74 | |||
1228 | 75 | #: md_importer/models.py:43 | ||
1229 | 76 | msgid "" | ||
1230 | 77 | "Article URL (for a specific file) or article namespace for a directory or a " | ||
1231 | 78 | "set of files." | ||
1232 | 79 | msgstr "" | ||
1233 | 80 | |||
1234 | 81 | #: md_importer/models.py:56 | ||
1235 | 82 | msgid "Datetime" | ||
1236 | 83 | msgstr "" | ||
1237 | 84 | |||
1238 | 85 | #: md_importer/models.py:56 | ||
1239 | 86 | msgid "Datetime of last import." | ||
1240 | 87 | msgstr "" | ||
1241 | 88 | |||
1242 | 71 | #: store_data/cms_plugins.py:11 | 89 | #: store_data/cms_plugins.py:11 |
1243 | 72 | msgid "Snap list - Gadget" | 90 | msgid "Snap list - Gadget" |
1244 | 73 | msgstr "" | 91 | msgstr "" |
1245 | @@ -101,9 +119,8 @@ | |||
1246 | 101 | 119 | ||
1247 | 102 | #: templates/base.html:34 templates/base.html.py:51 | 120 | #: templates/base.html:34 templates/base.html.py:51 |
1248 | 103 | #: templates/menu/menu_footer_first_level.html:8 | 121 | #: templates/menu/menu_footer_first_level.html:8 |
1252 | 104 | #: templates/menu/menu_header_first_level.html:8 | 122 | #: templates/menu/menu_header_first_level.html:8 templates/menu/sub_menu.html:8 |
1253 | 105 | #: templates/menu/sub_menu.html:8 templates/menu/sub_menu.html.py:22 | 123 | #: templates/menu/sub_menu.html.py:22 templates/menu/sub_menu.html:25 |
1251 | 106 | #: templates/menu/sub_menu.html:25 | ||
1254 | 107 | msgid "Overview" | 124 | msgid "Overview" |
1255 | 108 | msgstr "" | 125 | msgstr "" |
1256 | 109 | 126 | ||
1257 | @@ -175,15 +192,23 @@ | |||
1258 | 175 | msgid "Go to the top of the page" | 192 | msgid "Go to the top of the page" |
1259 | 176 | msgstr "" | 193 | msgstr "" |
1260 | 177 | 194 | ||
1261 | 195 | #: templates/comments/zinnia/entry/form.html:25 | ||
1262 | 196 | msgid "Comment as" | ||
1263 | 197 | msgstr "" | ||
1264 | 198 | |||
1265 | 199 | #: templates/comments/zinnia/entry/form.html:25 templates/error_base.html:63 | ||
1266 | 200 | #: templates/website_base.html:84 | ||
1267 | 201 | msgid "Log out" | ||
1268 | 202 | msgstr "" | ||
1269 | 203 | |||
1270 | 204 | #: templates/comments/zinnia/entry/form.html:28 | ||
1271 | 205 | msgid "Post" | ||
1272 | 206 | msgstr "" | ||
1273 | 207 | |||
1274 | 178 | #: templates/error_base.html:54 templates/website_base.html:74 | 208 | #: templates/error_base.html:54 templates/website_base.html:74 |
1275 | 179 | msgid "Jump to content" | 209 | msgid "Jump to content" |
1276 | 180 | msgstr "" | 210 | msgstr "" |
1277 | 181 | 211 | ||
1278 | 182 | #: templates/error_base.html:63 templates/website_base.html:84 | ||
1279 | 183 | #: templates/comments/zinnia/entry/form.html:25 | ||
1280 | 184 | msgid "Log out" | ||
1281 | 185 | msgstr "" | ||
1282 | 186 | |||
1283 | 187 | #: templates/error_base.html:69 templates/website_base.html:90 | 212 | #: templates/error_base.html:69 templates/website_base.html:90 |
1284 | 188 | msgid "Log in" | 213 | msgid "Log in" |
1285 | 189 | msgstr "" | 214 | msgstr "" |
1286 | @@ -204,6 +229,14 @@ | |||
1287 | 204 | "href='https://bugs.launchpad.net/ubuntudeveloperportal'>report it" | 229 | "href='https://bugs.launchpad.net/ubuntudeveloperportal'>report it" |
1288 | 205 | msgstr "" | 230 | msgstr "" |
1289 | 206 | 231 | ||
1290 | 232 | #: templates/menu/menu_language_chooser.html:6 | ||
1291 | 233 | msgid "View in:" | ||
1292 | 234 | msgstr "" | ||
1293 | 235 | |||
1294 | 236 | #: templates/menu/menu_language_chooser.html:10 | ||
1295 | 237 | msgid "Change to language:" | ||
1296 | 238 | msgstr "" | ||
1297 | 239 | |||
1298 | 207 | #: templates/search_results.html:4 templates/search_results.html.py:11 | 240 | #: templates/search_results.html:4 templates/search_results.html.py:11 |
1299 | 208 | msgid "Search results for:" | 241 | msgid "Search results for:" |
1300 | 209 | msgstr "" | 242 | msgstr "" |
1301 | @@ -224,8 +257,7 @@ | |||
1302 | 224 | msgid "A simple dashboard to keep track of updates between languages." | 257 | msgid "A simple dashboard to keep track of updates between languages." |
1303 | 225 | msgstr "" | 258 | msgstr "" |
1304 | 226 | 259 | ||
1307 | 227 | #: templates/translations_dashboard.html:11 | 260 | #: templates/translations_dashboard.html:11 templates/zinnia/entry_list.html:23 |
1306 | 228 | #: templates/zinnia/entry_list.html:23 | ||
1308 | 229 | msgid "Page" | 261 | msgid "Page" |
1309 | 230 | msgstr "" | 262 | msgstr "" |
1310 | 231 | 263 | ||
1311 | @@ -277,22 +309,6 @@ | |||
1312 | 277 | msgid "Back to top" | 309 | msgid "Back to top" |
1313 | 278 | msgstr "" | 310 | msgstr "" |
1314 | 279 | 311 | ||
1315 | 280 | #: templates/comments/zinnia/entry/form.html:25 | ||
1316 | 281 | msgid "Comment as" | ||
1317 | 282 | msgstr "" | ||
1318 | 283 | |||
1319 | 284 | #: templates/comments/zinnia/entry/form.html:28 | ||
1320 | 285 | msgid "Post" | ||
1321 | 286 | msgstr "" | ||
1322 | 287 | |||
1323 | 288 | #: templates/menu/menu_language_chooser.html:6 | ||
1324 | 289 | msgid "View in:" | ||
1325 | 290 | msgstr "" | ||
1326 | 291 | |||
1327 | 292 | #: templates/menu/menu_language_chooser.html:10 | ||
1328 | 293 | msgid "Change to language:" | ||
1329 | 294 | msgstr "" | ||
1330 | 295 | |||
1331 | 296 | #: templates/zinnia/_entry_detail_base.html:33 | 312 | #: templates/zinnia/_entry_detail_base.html:33 |
1332 | 297 | #, python-format | 313 | #, python-format |
1333 | 298 | msgid "%(percent)s%% of %(object)s still remains to read." | 314 | msgid "%(percent)s%% of %(object)s still remains to read." |
1334 | @@ -334,7 +350,7 @@ | |||
1335 | 334 | msgid "Authors" | 350 | msgid "Authors" |
1336 | 335 | msgstr "" | 351 | msgstr "" |
1337 | 336 | 352 | ||
1339 | 337 | #: templates/zinnia/author_list.html:21 templates/zinnia/category_list.html:19 | 353 | #: templates/zinnia/author_list.html:21 templates/zinnia/category_list.html:20 |
1340 | 338 | #: templates/zinnia/tag_list.html:20 | 354 | #: templates/zinnia/tag_list.html:20 |
1341 | 339 | #, python-format | 355 | #, python-format |
1342 | 340 | msgid "%(entry_count)s entry" | 356 | msgid "%(entry_count)s entry" |
1343 | @@ -354,21 +370,20 @@ | |||
1344 | 354 | msgid "RSS Feed of latest discussions" | 370 | msgid "RSS Feed of latest discussions" |
1345 | 355 | msgstr "" | 371 | msgstr "" |
1346 | 356 | 372 | ||
1349 | 357 | #: templates/zinnia/category_list.html:4 | 373 | #: templates/zinnia/category_list.html:5 templates/zinnia/category_list.html:13 |
1348 | 358 | #: templates/zinnia/category_list.html:12 | ||
1350 | 359 | msgid "Category list" | 374 | msgid "Category list" |
1351 | 360 | msgstr "" | 375 | msgstr "" |
1352 | 361 | 376 | ||
1354 | 362 | #: templates/zinnia/category_list.html:6 | 377 | #: templates/zinnia/category_list.html:7 |
1355 | 363 | msgid "Categories" | 378 | msgid "Categories" |
1356 | 364 | msgstr "" | 379 | msgstr "" |
1357 | 365 | 380 | ||
1359 | 366 | #: templates/zinnia/category_list.html:18 | 381 | #: templates/zinnia/category_list.html:19 |
1360 | 367 | #, python-format | 382 | #, python-format |
1361 | 368 | msgid "Show all entries in %(category)s" | 383 | msgid "Show all entries in %(category)s" |
1362 | 369 | msgstr "" | 384 | msgstr "" |
1363 | 370 | 385 | ||
1365 | 371 | #: templates/zinnia/category_list.html:24 | 386 | #: templates/zinnia/category_list.html:25 |
1366 | 372 | msgid "No categories yet." | 387 | msgid "No categories yet." |
1367 | 373 | msgstr "" | 388 | msgstr "" |
1368 | 374 | 389 | ||
1369 | 375 | 390 | ||
1370 | === added directory 'md_importer' | |||
1371 | === added file 'md_importer/TODO' | |||
1372 | --- md_importer/TODO 1970-01-01 00:00:00 +0000 | |||
1373 | +++ md_importer/TODO 2016-01-19 00:21:38 +0000 | |||
1374 | @@ -0,0 +1,7 @@ | |||
1375 | 1 | - fix branch indicator (devel vs. stable, 1.x vs devel, etc.) | ||
1376 | 2 | - unpublished docs: some articles show up with a blue circle, so | ||
1377 | 3 | unpublished although they're accessible (not a real issue - content is | ||
1378 | 4 | always updated correctly) | ||
1379 | 5 | - add comment at top of RawHTML plugins, so users know not to edit them, | ||
1380 | 6 | currently blocked on LP: #1523925 (RawHTML plugin strips comments) | ||
1381 | 7 | - investigate what the api importer does (upload to swift) | ||
1382 | 0 | 8 | ||
1383 | === added file 'md_importer/__init__.py' | |||
1384 | === added file 'md_importer/admin.py' | |||
1385 | --- md_importer/admin.py 1970-01-01 00:00:00 +0000 | |||
1386 | +++ md_importer/admin.py 2016-01-19 00:21:38 +0000 | |||
1387 | @@ -0,0 +1,36 @@ | |||
1388 | 1 | from django.contrib import admin | ||
1389 | 2 | |||
1390 | 3 | from .models import ( | ||
1391 | 4 | ExternalDocsBranch, ExternalDocsBranchImportDirective, | ||
1392 | 5 | ImportedArticle, | ||
1393 | 6 | ) | ||
1394 | 7 | from django.core.management import call_command | ||
1395 | 8 | |||
1396 | 9 | __all__ = ( | ||
1397 | 10 | ) | ||
1398 | 11 | |||
1399 | 12 | |||
1400 | 13 | def import_selected_external_docs_branches(modeladmin, request, queryset): | ||
1401 | 14 | branches = [] | ||
1402 | 15 | for branch in queryset: | ||
1403 | 16 | branches.append(branch.origin) | ||
1404 | 17 | call_command('import_md', *branches) | ||
1405 | 18 | import_selected_external_docs_branches.short_description = \ | ||
1406 | 19 | "Import selected branches" | ||
1407 | 20 | |||
1408 | 21 | |||
1409 | 22 | @admin.register(ExternalDocsBranch) | ||
1410 | 23 | class ExternalDocsBranchAdmin(admin.ModelAdmin): | ||
1411 | 24 | list_display = ('origin', 'post_checkout_command', 'branch_name',) | ||
1412 | 25 | list_filter = ('origin', 'post_checkout_command', 'branch_name',) | ||
1413 | 26 | actions = [import_selected_external_docs_branches] | ||
1414 | 27 | |||
1415 | 28 | |||
1416 | 29 | @admin.register(ExternalDocsBranchImportDirective) | ||
1417 | 30 | class ExternalDocsBranchImportDirectiveAdmin(admin.ModelAdmin): | ||
1418 | 31 | pass | ||
1419 | 32 | |||
1420 | 33 | |||
1421 | 34 | @admin.register(ImportedArticle) | ||
1422 | 35 | class ImportedArticleAdmin(admin.ModelAdmin): | ||
1423 | 36 | pass | ||
1424 | 0 | 37 | ||
1425 | === added directory 'md_importer/importer' | |||
1426 | === added file 'md_importer/importer/__init__.py' | |||
1427 | --- md_importer/importer/__init__.py 1970-01-01 00:00:00 +0000 | |||
1428 | +++ md_importer/importer/__init__.py 2016-01-19 00:21:38 +0000 | |||
1429 | @@ -0,0 +1,5 @@ | |||
1430 | 1 | from developer_portal.settings import LANGUAGE_CODE | ||
1431 | 2 | |||
1432 | 3 | DEFAULT_LANG = LANGUAGE_CODE | ||
1433 | 4 | HOME_PAGE_URL = '/{}/'.format(DEFAULT_LANG) | ||
1434 | 5 | SUPPORTED_ARTICLE_TYPES = ['.md', '.html'] | ||
1435 | 0 | 6 | ||
1436 | === added file 'md_importer/importer/article.py' | |||
1437 | --- md_importer/importer/article.py 1970-01-01 00:00:00 +0000 | |||
1438 | +++ md_importer/importer/article.py 2016-01-19 00:21:38 +0000 | |||
1439 | @@ -0,0 +1,163 @@ | |||
1440 | 1 | from bs4 import BeautifulSoup | ||
1441 | 2 | import codecs | ||
1442 | 3 | import logging | ||
1443 | 4 | import markdown | ||
1444 | 5 | import os | ||
1445 | 6 | import re | ||
1446 | 7 | import sys | ||
1447 | 8 | |||
1448 | 9 | from . import ( | ||
1449 | 10 | DEFAULT_LANG, | ||
1450 | 11 | SUPPORTED_ARTICLE_TYPES, | ||
1451 | 12 | ) | ||
1452 | 13 | from .publish import get_or_create_page, slugify | ||
1453 | 14 | |||
1454 | 15 | if sys.version_info.major == 2: | ||
1455 | 16 | from urlparse import urlparse | ||
1456 | 17 | else: | ||
1457 | 18 | from urllib.parse import urlparse | ||
1458 | 19 | |||
1459 | 20 | |||
1460 | 21 | class Article: | ||
1461 | 22 | def __init__(self, fn, write_to): | ||
1462 | 23 | self.html = None | ||
1463 | 24 | self.page = None | ||
1464 | 25 | self.title = "" | ||
1465 | 26 | self.fn = fn | ||
1466 | 27 | self.write_to = slugify(self.fn) | ||
1467 | 28 | self.full_url = write_to | ||
1468 | 29 | self.slug = os.path.basename(self.full_url) | ||
1469 | 30 | |||
1470 | 31 | def _find_local_images(self): | ||
1471 | 32 | '''Local images are currently not supported.''' | ||
1472 | 33 | soup = BeautifulSoup(self.html, 'html5lib') | ||
1473 | 34 | local_images = [] | ||
1474 | 35 | for img in soup.find_all('img'): | ||
1475 | 36 | if img.has_attr('src'): | ||
1476 | 37 | (scheme, netloc, path, params, query, fragment) = \ | ||
1477 | 38 | urlparse(img.attrs['src']) | ||
1478 | 39 | if scheme not in ['http', 'https']: | ||
1479 | 40 | local_images.extend([img.attrs['src']]) | ||
1480 | 41 | return local_images | ||
1481 | 42 | |||
1482 | 43 | def read(self): | ||
1483 | 44 | if os.path.splitext(self.fn)[1] not in SUPPORTED_ARTICLE_TYPES: | ||
1484 | 45 | logging.error("Don't know how to interpret '{}'.".format( | ||
1485 | 46 | self.fn)) | ||
1486 | 47 | return False | ||
1487 | 48 | with codecs.open(self.fn, 'r', encoding='utf-8') as f: | ||
1488 | 49 | if self.fn.endswith('.md'): | ||
1489 | 50 | self.html = markdown.markdown( | ||
1490 | 51 | f.read(), | ||
1491 | 52 | output_format='html5', | ||
1492 | 53 | extensions=['pymdownx.github']) | ||
1493 | 54 | elif self.fn.endswith('.html'): | ||
1494 | 55 | self.html = f.read() | ||
1495 | 56 | local_images = self._find_local_images() | ||
1496 | 57 | if local_images: | ||
1497 | 58 | logging.error('Found the following local image(s): {}'.format( | ||
1498 | 59 | ', '.join(local_images) | ||
1499 | 60 | )) | ||
1500 | 61 | return False | ||
1501 | 62 | self.title = self._read_title() | ||
1502 | 63 | self._remove_body_and_html_tags() | ||
1503 | 64 | self._use_developer_site_style() | ||
1504 | 65 | return True | ||
1505 | 66 | |||
1506 | 67 | def _read_title(self): | ||
1507 | 68 | soup = BeautifulSoup(self.html, 'html5lib') | ||
1508 | 69 | if soup.title: | ||
1509 | 70 | return soup.title.text | ||
1510 | 71 | if soup.h1: | ||
1511 | 72 | return soup.h1.text | ||
1512 | 73 | return slugify(self.fn).replace('-', ' ').title() | ||
1513 | 74 | |||
1514 | 75 | def _remove_body_and_html_tags(self): | ||
1515 | 76 | self.html = re.sub(r"<html>\n\s<body>\n", "", self.html, | ||
1516 | 77 | flags=re.MULTILINE) | ||
1517 | 78 | self.html = re.sub(r"\s<\/body>\n<\/html>", "", self.html, | ||
1518 | 79 | flags=re.MULTILINE) | ||
1519 | 80 | |||
1520 | 81 | def _use_developer_site_style(self): | ||
1521 | 82 | begin = (u"<div class=\"row no-border\">" | ||
1522 | 83 | "\n<div class=\"eight-col\">\n") | ||
1523 | 84 | end = u"</div>\n</div>" | ||
1524 | 85 | self.html = begin + self.html + end | ||
1525 | 86 | self.html = self.html.replace( | ||
1526 | 87 | "<pre><code>", | ||
1527 | 88 | "</div><div class=\"twelve-col\"><pre><code>") | ||
1528 | 89 | self.html = self.html.replace( | ||
1529 | 90 | "</code></pre>", | ||
1530 | 91 | "</code></pre></div><div class=\"eight-col\">") | ||
1531 | 92 | |||
1532 | 93 | def replace_links(self, titles, url_map): | ||
1533 | 94 | soup = BeautifulSoup(self.html, 'html5lib') | ||
1534 | 95 | change = False | ||
1535 | 96 | for link in soup.find_all('a'): | ||
1536 | 97 | if not link.has_attr('class') or \ | ||
1537 | 98 | 'headeranchor-link' not in link.attrs['class']: | ||
1538 | 99 | for title in titles: | ||
1539 | 100 | if title.endswith(link.attrs['href']) and \ | ||
1540 | 101 | link.attrs['href'] != url_map[title].full_url: | ||
1541 | 102 | link.attrs['href'] = url_map[title].full_url | ||
1542 | 103 | change = True | ||
1543 | 104 | if change: | ||
1544 | 105 | self.html = soup.prettify() | ||
1545 | 106 | return change | ||
1546 | 107 | |||
1547 | 108 | def add_to_db(self): | ||
1548 | 109 | '''Publishes pages in their branch alias namespace.''' | ||
1549 | 110 | self.page = get_or_create_page( | ||
1550 | 111 | title=self.title, full_url=self.full_url, menu_title=self.title, | ||
1551 | 112 | html=self.html) | ||
1552 | 113 | if not self.page: | ||
1553 | 114 | return False | ||
1554 | 115 | self.full_url = self.page.get_absolute_url() | ||
1555 | 116 | return True | ||
1556 | 117 | |||
1557 | 118 | def publish(self): | ||
1558 | 119 | if self.page.is_dirty(DEFAULT_LANG): | ||
1559 | 120 | self.page.publish(DEFAULT_LANG) | ||
1560 | 121 | self.page = self.page.get_public_object() | ||
1561 | 122 | return self.page | ||
1562 | 123 | |||
1563 | 124 | |||
1564 | 125 | class SnappyArticle(Article): | ||
1565 | 126 | release_alias = None | ||
1566 | 127 | |||
1567 | 128 | def read(self): | ||
1568 | 129 | if not Article.read(self): | ||
1569 | 130 | return False | ||
1570 | 131 | self.release_alias = re.findall(r'snappy/guides/(\S+?)/\S+?', | ||
1571 | 132 | self.full_url)[0] | ||
1572 | 133 | self._make_snappy_mods() | ||
1573 | 134 | return True | ||
1574 | 135 | |||
1575 | 136 | def _make_snappy_mods(self): | ||
1576 | 137 | # Make sure the reader knows which documentation she is browsing | ||
1577 | 138 | if self.release_alias != 'current': | ||
1578 | 139 | before = (u"<div class=\"row no-border\">\n" | ||
1579 | 140 | "<div class=\"eight-col\">\n") | ||
1580 | 141 | after = (u"<div class=\"row no-border\">\n" | ||
1581 | 142 | "<div class=\"box pull-three three-col\">" | ||
1582 | 143 | "<p>You are browsing the Snappy <code>%s</code> " | ||
1583 | 144 | "documentation.</p>" | ||
1584 | 145 | "<p><a href=\"/snappy/guides/current/%s\">" | ||
1585 | 146 | "Back to the latest stable release ›" | ||
1586 | 147 | "</a></p></div>\n" | ||
1587 | 148 | "<div class=\"eight-col\">\n") % (self.release_alias, | ||
1588 | 149 | self.slug, ) | ||
1589 | 150 | self.html = self.html.replace(before, after) | ||
1590 | 151 | |||
1591 | 152 | def add_to_db(self): | ||
1592 | 153 | if self.release_alias == "current": | ||
1593 | 154 | # Add a guides/<page> redirect to guides/current/<page> | ||
1594 | 155 | page = get_or_create_page( | ||
1595 | 156 | title=self.title, | ||
1596 | 157 | full_url=self.full_url.replace('/current', ''), | ||
1597 | 158 | redirect="/snappy/guides/current/{}".format(self.slug)) | ||
1598 | 159 | if not page: | ||
1599 | 160 | return False | ||
1600 | 161 | else: | ||
1601 | 162 | self.title += " (%s)" % (self.release_alias,) | ||
1602 | 163 | return Article.add_to_db(self) | ||
1603 | 0 | 164 | ||
1604 | === added file 'md_importer/importer/process.py' | |||
1605 | --- md_importer/importer/process.py 1970-01-01 00:00:00 +0000 | |||
1606 | +++ md_importer/importer/process.py 2016-01-19 00:21:38 +0000 | |||
1607 | @@ -0,0 +1,60 @@ | |||
1608 | 1 | from django.core.management import call_command | ||
1609 | 2 | |||
1610 | 3 | import datetime | ||
1611 | 4 | import pytz | ||
1612 | 5 | import shutil | ||
1613 | 6 | import tempfile | ||
1614 | 7 | |||
1615 | 8 | from md_importer.importer.repo import create_repo | ||
1616 | 9 | |||
1617 | 10 | from md_importer.models import ( | ||
1618 | 11 | ExternalDocsBranchImportDirective, | ||
1619 | 12 | ImportedArticle, | ||
1620 | 13 | ) | ||
1621 | 14 | |||
1622 | 15 | |||
1623 | 16 | def process_branch(branch): | ||
1624 | 17 | tempdir = tempfile.mkdtemp() | ||
1625 | 18 | repo = create_repo(tempdir, branch.origin, branch.branch_name, | ||
1626 | 19 | branch.post_checkout_command) | ||
1627 | 20 | if repo.get() != 0: | ||
1628 | 21 | return False | ||
1629 | 22 | for directive in ExternalDocsBranchImportDirective.objects.filter( | ||
1630 | 23 | external_docs_branch=branch): | ||
1631 | 24 | repo.add_directive(directive.import_from, | ||
1632 | 25 | directive.write_to) | ||
1633 | 26 | if not repo.execute_import_directives(): | ||
1634 | 27 | return False | ||
1635 | 28 | if not repo.publish(): | ||
1636 | 29 | return False | ||
1637 | 30 | timestamp = datetime.datetime.now(pytz.utc) | ||
1638 | 31 | |||
1639 | 32 | # Update data in ImportedArticle table | ||
1640 | 33 | for page in repo.pages: | ||
1641 | 34 | if ImportedArticle.objects.filter(branch=branch, page=page).count(): | ||
1642 | 35 | imported_article = ImportedArticle.objects.filter( | ||
1643 | 36 | branch=branch, page=page)[0] | ||
1644 | 37 | imported_article.last_import = datetime.datetime.now(pytz.utc) | ||
1645 | 38 | imported_article.save() | ||
1646 | 39 | else: | ||
1647 | 40 | ImportedArticle.objects.get_or_create( | ||
1648 | 41 | branch=branch, | ||
1649 | 42 | page=page, | ||
1650 | 43 | last_import=datetime.datetime.now(pytz.utc)) | ||
1651 | 44 | |||
1652 | 45 | # Remove old entries | ||
1653 | 46 | for imported_article in ImportedArticle.objects.filter( | ||
1654 | 47 | branch=branch, last_import__lt=timestamp): | ||
1655 | 48 | imported_article.page.delete() | ||
1656 | 49 | imported_article.delete() | ||
1657 | 50 | |||
1658 | 51 | # The import is done, now let's clean up. | ||
1659 | 52 | imported_page_ids = [p.id for p in repo.pages | ||
1660 | 53 | if p.changed_by in ['python-api', 'script']] | ||
1661 | 54 | ImportedArticle.objects.filter( | ||
1662 | 55 | branch=branch).filter(id__in=imported_page_ids).delete() | ||
1663 | 56 | shutil.rmtree(tempdir) | ||
1664 | 57 | |||
1665 | 58 | # https://stackoverflow.com/questions/33284171/ | ||
1666 | 59 | call_command('cms', 'fix-tree') | ||
1667 | 60 | return True | ||
1668 | 0 | 61 | ||
1669 | === added file 'md_importer/importer/publish.py' | |||
1670 | --- md_importer/importer/publish.py 1970-01-01 00:00:00 +0000 | |||
1671 | +++ md_importer/importer/publish.py 2016-01-19 00:21:38 +0000 | |||
1672 | @@ -0,0 +1,75 @@ | |||
1673 | 1 | from md_importer.importer import DEFAULT_LANG, HOME_PAGE_URL | ||
1674 | 2 | |||
1675 | 3 | from cms.api import create_page, add_plugin | ||
1676 | 4 | from cms.models import Title | ||
1677 | 5 | from djangocms_text_ckeditor.html import clean_html | ||
1678 | 6 | |||
1679 | 7 | import logging | ||
1680 | 8 | import re | ||
1681 | 9 | import os | ||
1682 | 10 | |||
1683 | 11 | |||
1684 | 12 | def slugify(filename): | ||
1685 | 13 | return os.path.basename(filename).replace('.md', '').replace('.html', '') | ||
1686 | 14 | |||
1687 | 15 | |||
1688 | 16 | def _find_parent(full_url): | ||
1689 | 17 | if full_url == HOME_PAGE_URL: | ||
1690 | 18 | # If we set up the homepage, we don't need a parent. | ||
1691 | 19 | return None | ||
1692 | 20 | parent_url = re.sub( | ||
1693 | 21 | r'^\/{}\/'.format(DEFAULT_LANG), | ||
1694 | 22 | '', | ||
1695 | 23 | os.path.dirname(full_url)) | ||
1696 | 24 | |||
1697 | 25 | parent_pages = Title.objects.select_related('page').filter( | ||
1698 | 26 | path__regex=parent_url, language=DEFAULT_LANG).filter( | ||
1699 | 27 | publisher_is_draft=True) | ||
1700 | 28 | if not parent_pages: | ||
1701 | 29 | logging.error('Parent {} not found.'.format( | ||
1702 | 30 | parent_url)) | ||
1703 | 31 | return None | ||
1704 | 32 | return parent_pages[0].page | ||
1705 | 33 | |||
1706 | 34 | |||
1707 | 35 | def get_or_create_page(title, full_url, menu_title=None, | ||
1708 | 36 | in_navigation=True, redirect=None, html=None): | ||
1709 | 37 | # First check if pages already exist. | ||
1710 | 38 | pages = Title.objects.select_related('page').filter( | ||
1711 | 39 | path__regex=full_url).filter(publisher_is_draft=True) | ||
1712 | 40 | if pages: | ||
1713 | 41 | page = pages[0].page | ||
1714 | 42 | if page.get_title() != title: | ||
1715 | 43 | page.title = title | ||
1716 | 44 | if page.get_menu_title() != menu_title: | ||
1717 | 45 | page.menu_title = menu_title | ||
1718 | 46 | if page.in_navigation != in_navigation: | ||
1719 | 47 | page.in_navigation = in_navigation | ||
1720 | 48 | if page.get_redirect() != redirect: | ||
1721 | 49 | page.redirect = redirect | ||
1722 | 50 | if html: | ||
1723 | 51 | # We create the page, so we know there's just one placeholder | ||
1724 | 52 | placeholder = page.placeholders.all()[0] | ||
1725 | 53 | if placeholder.get_plugins(): | ||
1726 | 54 | plugin = placeholder.get_plugins()[0].get_plugin_instance()[0] | ||
1727 | 55 | if plugin.body != clean_html(html, full=False): | ||
1728 | 56 | plugin.body = html | ||
1729 | 57 | plugin.save() | ||
1730 | 58 | else: | ||
1731 | 59 | add_plugin( | ||
1732 | 60 | placeholder, 'RawHtmlPlugin', | ||
1733 | 61 | DEFAULT_LANG, body=html) | ||
1734 | 62 | else: | ||
1735 | 63 | parent = _find_parent(full_url) | ||
1736 | 64 | if not parent: | ||
1737 | 65 | return None | ||
1738 | 66 | slug = os.path.basename(full_url) | ||
1739 | 67 | page = create_page( | ||
1740 | 68 | title, 'default.html', DEFAULT_LANG, slug=slug, parent=parent, | ||
1741 | 69 | menu_title=menu_title, in_navigation=in_navigation, | ||
1742 | 70 | position='last-child', redirect=redirect) | ||
1743 | 71 | placeholder = page.placeholders.get() | ||
1744 | 72 | add_plugin(placeholder, 'RawHtmlPlugin', DEFAULT_LANG, body=html) | ||
1745 | 73 | placeholder = page.placeholders.all()[0] | ||
1746 | 74 | plugin = placeholder.get_plugins()[0].get_plugin_instance()[0] | ||
1747 | 75 | return page | ||
1748 | 0 | 76 | ||
1749 | === added file 'md_importer/importer/repo.py' | |||
1750 | --- md_importer/importer/repo.py 1970-01-01 00:00:00 +0000 | |||
1751 | +++ md_importer/importer/repo.py 2016-01-19 00:21:38 +0000 | |||
1752 | @@ -0,0 +1,174 @@ | |||
1753 | 1 | from . import ( | ||
1754 | 2 | DEFAULT_LANG, | ||
1755 | 3 | SUPPORTED_ARTICLE_TYPES, | ||
1756 | 4 | ) | ||
1757 | 5 | from .article import Article, SnappyArticle | ||
1758 | 6 | from .publish import get_or_create_page, slugify | ||
1759 | 7 | from .source import SourceCode | ||
1760 | 8 | |||
1761 | 9 | import glob | ||
1762 | 10 | import logging | ||
1763 | 11 | import os | ||
1764 | 12 | |||
1765 | 13 | |||
1766 | 14 | def create_repo(tempdir, origin, branch_name, post_checkout_command): | ||
1767 | 15 | if os.path.exists(origin): | ||
1768 | 16 | if 'snappy' in origin: | ||
1769 | 17 | repo_class = SnappyRepo | ||
1770 | 18 | else: | ||
1771 | 19 | repo_class = Repo | ||
1772 | 20 | else: | ||
1773 | 21 | if origin.startswith('lp:snappy') or \ | ||
1774 | 22 | 'snappy' in origin.split(':')[1].split('.git')[0].split('/'): | ||
1775 | 23 | repo_class = SnappyRepo | ||
1776 | 24 | else: | ||
1777 | 25 | repo_class = Repo | ||
1778 | 26 | return repo_class(tempdir, origin, branch_name, post_checkout_command) | ||
1779 | 27 | |||
1780 | 28 | |||
1781 | 29 | class Repo: | ||
1782 | 30 | def __init__(self, tempdir, origin, branch_name, post_checkout_command): | ||
1783 | 31 | self.directives = [] | ||
1784 | 32 | self.imported_articles = [] | ||
1785 | 33 | self.url_map = {} | ||
1786 | 34 | self.titles = {} | ||
1787 | 35 | self.index_doc_url = None | ||
1788 | 36 | self.index_page = None | ||
1789 | 37 | self.release_alias = None | ||
1790 | 38 | # On top of the pages in imported_articles this also | ||
1791 | 39 | # includes index_page | ||
1792 | 40 | self.pages = [] | ||
1793 | 41 | self.origin = origin | ||
1794 | 42 | self.branch_name = branch_name | ||
1795 | 43 | self.post_checkout_command = post_checkout_command | ||
1796 | 44 | branch_nick = os.path.basename(self.origin.replace('.git', '')) | ||
1797 | 45 | self.checkout_location = os.path.join( | ||
1798 | 46 | tempdir, branch_nick) | ||
1799 | 47 | self.index_doc_title = branch_nick | ||
1800 | 48 | self.article_class = Article | ||
1801 | 49 | |||
1802 | 50 | def get(self): | ||
1803 | 51 | sourcecode = SourceCode(self.origin, self.checkout_location, | ||
1804 | 52 | self.branch_name, self.post_checkout_command) | ||
1805 | 53 | if sourcecode.get() != 0: | ||
1806 | 54 | logging.error( | ||
1807 | 55 | 'Could not check out branch "{}".'.format(self.origin)) | ||
1808 | 56 | return 1 | ||
1809 | 57 | return 0 | ||
1810 | 58 | |||
1811 | 59 | def add_directive(self, import_from, write_to): | ||
1812 | 60 | self.directives += [ | ||
1813 | 61 | { | ||
1814 | 62 | 'import_from': os.path.join(self.checkout_location, | ||
1815 | 63 | import_from), | ||
1816 | 64 | 'write_to': write_to | ||
1817 | 65 | } | ||
1818 | 66 | ] | ||
1819 | 67 | |||
1820 | 68 | def execute_import_directives(self): | ||
1821 | 69 | import_list = [] | ||
1822 | 70 | # Import single files first | ||
1823 | 71 | for directive in [d for d in self.directives | ||
1824 | 72 | if os.path.isfile(d['import_from'])]: | ||
1825 | 73 | import_list += [ | ||
1826 | 74 | (directive['import_from'], directive['write_to']) | ||
1827 | 75 | ] | ||
1828 | 76 | # Import directories next | ||
1829 | 77 | for directive in [d for d in self.directives | ||
1830 | 78 | if os.path.isdir(d['import_from'])]: | ||
1831 | 79 | for fn in glob.glob('{}/*'.format(directive['import_from'])): | ||
1832 | 80 | if fn not in [a[0] for a in import_list]: | ||
1833 | 81 | import_list += [ | ||
1834 | 82 | (fn, os.path.join(directive['write_to'], slugify(fn))) | ||
1835 | 83 | ] | ||
1836 | 84 | # If we import into a namespace and don't have an index doc, | ||
1837 | 85 | # we need to write one. | ||
1838 | 86 | if directive['write_to'] not in [x[1] for x in import_list]: | ||
1839 | 87 | self.index_doc_url = directive['write_to'] | ||
1840 | 88 | if self.index_doc_url: | ||
1841 | 89 | if not self._create_fake_index_page(): | ||
1842 | 90 | logging.error('Importing of {} aborted.'.format(self.origin)) | ||
1843 | 91 | return False | ||
1844 | 92 | # The actual import | ||
1845 | 93 | for entry in import_list: | ||
1846 | 94 | article = self._read_article(entry[0], entry[1]) | ||
1847 | 95 | if article: | ||
1848 | 96 | self.imported_articles += [article] | ||
1849 | 97 | self.titles[article.fn] = article.title | ||
1850 | 98 | self.url_map[article.fn] = article | ||
1851 | 99 | elif os.path.splitext(entry[0])[1] in SUPPORTED_ARTICLE_TYPES: | ||
1852 | 100 | # In this case the article was supported but still reading | ||
1853 | 101 | # it failed, importing should be stopped here to avoid | ||
1854 | 102 | # problems. | ||
1855 | 103 | logging.error('Importing of {} aborted.'.format(self.origin)) | ||
1856 | 104 | return False | ||
1857 | 105 | if self.index_doc_url: | ||
1858 | 106 | self._write_fake_index_doc() | ||
1859 | 107 | return True | ||
1860 | 108 | |||
1861 | 109 | def _read_article(self, fn, write_to): | ||
1862 | 110 | article = self.article_class(fn, write_to) | ||
1863 | 111 | if article.read(): | ||
1864 | 112 | return article | ||
1865 | 113 | return None | ||
1866 | 114 | |||
1867 | 115 | def publish(self): | ||
1868 | 116 | for article in self.imported_articles: | ||
1869 | 117 | if not article.add_to_db(): | ||
1870 | 118 | logging.error('Publishing of {} aborted.'.format(self.origin)) | ||
1871 | 119 | return False | ||
1872 | 120 | article.replace_links(self.titles, self.url_map) | ||
1873 | 121 | self.pages = [] | ||
1874 | 122 | for article in self.imported_articles: | ||
1875 | 123 | self.pages.extend([article.publish()]) | ||
1876 | 124 | if self.index_page: | ||
1877 | 125 | self.index_page.publish(DEFAULT_LANG) | ||
1878 | 126 | self.pages.extend([self.index_page]) | ||
1879 | 127 | return True | ||
1880 | 128 | |||
1881 | 129 | def _create_fake_index_page(self): | ||
1882 | 130 | '''Creates a fake index page at the top of the branches | ||
1883 | 131 | docs namespace.''' | ||
1884 | 132 | |||
1885 | 133 | if self.index_doc_url.endswith('current'): | ||
1886 | 134 | redirect = '/snappy/guides' | ||
1887 | 135 | else: | ||
1888 | 136 | redirect = None | ||
1889 | 137 | self.index_page = get_or_create_page( | ||
1890 | 138 | title=self.index_doc_title, full_url=self.index_doc_url, | ||
1891 | 139 | in_navigation=False, redirect=redirect, html='', | ||
1892 | 140 | menu_title=None) | ||
1893 | 141 | if not self.index_page: | ||
1894 | 142 | return False | ||
1895 | 143 | return True | ||
1896 | 144 | |||
1897 | 145 | def _write_fake_index_doc(self): | ||
1898 | 146 | list_pages = '' | ||
1899 | 147 | for article in [a for a | ||
1900 | 148 | in self.imported_articles | ||
1901 | 149 | if a.full_url.startswith(self.index_doc_url)]: | ||
1902 | 150 | list_pages += '<li><a href=\"{}\">{}</a></li>'.format( | ||
1903 | 151 | os.path.basename(article.full_url), article.title) | ||
1904 | 152 | self.index_page.html = ( | ||
1905 | 153 | u'<div class=\"row\"><div class=\"eight-col\">\n' | ||
1906 | 154 | '<p>This section contains documentation for the ' | ||
1907 | 155 | '<code>{}</code> Snappy branch.</p>' | ||
1908 | 156 | '<p><ul class=\"list-ubuntu\">{}</ul></p>\n' | ||
1909 | 157 | '<p>Auto-imported from <a ' | ||
1910 | 158 | 'href=\"{}\">{}</a>.</p>\n' | ||
1911 | 159 | '</div></div>'.format(self.release_alias, list_pages, | ||
1912 | 160 | self.origin, self.origin)) | ||
1913 | 161 | |||
1914 | 162 | |||
1915 | 163 | class SnappyRepo(Repo): | ||
1916 | 164 | def __init__(self, tempdir, origin, branch_name, post_checkout_command): | ||
1917 | 165 | Repo.__init__(self, tempdir, origin, branch_name, | ||
1918 | 166 | post_checkout_command) | ||
1919 | 167 | self.article_class = SnappyArticle | ||
1920 | 168 | self.index_doc_title = 'Snappy documentation' | ||
1921 | 169 | |||
1922 | 170 | def _create_fake_index_page(self): | ||
1923 | 171 | self.release_alias = os.path.basename(self.index_doc_url) | ||
1924 | 172 | if not self.index_doc_url.endswith('current'): | ||
1925 | 173 | self.index_doc_title += ' ({})'.format(self.release_alias) | ||
1926 | 174 | return Repo._create_fake_index_page(self) | ||
1927 | 0 | 175 | ||
1928 | === added file 'md_importer/importer/source.py' | |||
1929 | --- md_importer/importer/source.py 1970-01-01 00:00:00 +0000 | |||
1930 | +++ md_importer/importer/source.py 2016-01-19 00:21:38 +0000 | |||
1931 | @@ -0,0 +1,60 @@ | |||
1932 | 1 | import logging | ||
1933 | 2 | import os | ||
1934 | 3 | import shutil | ||
1935 | 4 | import subprocess | ||
1936 | 5 | |||
1937 | 6 | |||
1938 | 7 | class SourceCode(): | ||
1939 | 8 | def __init__(self, origin, checkout_location, branch_name, | ||
1940 | 9 | post_checkout_command): | ||
1941 | 10 | self.origin = origin | ||
1942 | 11 | self.checkout_location = checkout_location | ||
1943 | 12 | self.branch_name = branch_name | ||
1944 | 13 | self.post_checkout_command = post_checkout_command | ||
1945 | 14 | |||
1946 | 15 | def get(self): | ||
1947 | 16 | res = self._get_repo() | ||
1948 | 17 | if res == 0 and self.post_checkout_command: | ||
1949 | 18 | res = self._post_checkout() | ||
1950 | 19 | return res | ||
1951 | 20 | return res | ||
1952 | 21 | |||
1953 | 22 | def _get_repo(self): | ||
1954 | 23 | if os.path.exists(self.origin): | ||
1955 | 24 | shutil.copytree( | ||
1956 | 25 | self.origin, | ||
1957 | 26 | self.checkout_location) | ||
1958 | 27 | return 0 | ||
1959 | 28 | if self.origin.startswith('lp:') and \ | ||
1960 | 29 | os.path.exists('/usr/bin/bzr'): | ||
1961 | 30 | return subprocess.call([ | ||
1962 | 31 | 'bzr', 'checkout', '--lightweight', self.origin, | ||
1963 | 32 | self.checkout_location]) | ||
1964 | 33 | if self.origin.startswith('https://github.com') and \ | ||
1965 | 34 | self.origin.endswith('.git') and \ | ||
1966 | 35 | os.path.exists('/usr/bin/git'): | ||
1967 | 36 | retcode = subprocess.call([ | ||
1968 | 37 | 'git', 'clone', '--quiet', self.origin, | ||
1969 | 38 | self.checkout_location]) | ||
1970 | 39 | if retcode == 0 and self.branch_name: | ||
1971 | 40 | pwd = os.getcwd() | ||
1972 | 41 | os.chdir(self.checkout_location) | ||
1973 | 42 | retcode = subprocess.call(['git', 'checkout', '--quiet', | ||
1974 | 43 | self.branch_name]) | ||
1975 | 44 | os.chdir(pwd) | ||
1976 | 45 | return retcode | ||
1977 | 46 | logging.error( | ||
1978 | 47 | 'Repo format "{}" not understood.'.format(self.origin)) | ||
1979 | 48 | return 1 | ||
1980 | 49 | |||
1981 | 50 | def _post_checkout(self): | ||
1982 | 51 | pwd = os.getcwd() | ||
1983 | 52 | os.chdir(self.checkout_location) | ||
1984 | 53 | process = subprocess.Popen(self.post_checkout_command.split(), | ||
1985 | 54 | stdout=subprocess.PIPE) | ||
1986 | 55 | (out, err) = process.communicate() | ||
1987 | 56 | retcode = process.wait() | ||
1988 | 57 | os.chdir(pwd) | ||
1989 | 58 | if retcode != 0: | ||
1990 | 59 | logging.error(out) | ||
1991 | 60 | return retcode | ||
1992 | 0 | 61 | ||
1993 | === added directory 'md_importer/management' | |||
1994 | === added file 'md_importer/management/__init__.py' | |||
1995 | === added directory 'md_importer/management/commands' | |||
1996 | === added file 'md_importer/management/commands/__init__.py' | |||
1997 | === renamed file 'developer_portal/management/commands/import-external-docs-branches.py' => 'md_importer/management/commands/import_md.py' | |||
1998 | --- developer_portal/management/commands/import-external-docs-branches.py 2015-12-08 10:25:29 +0000 | |||
1999 | +++ md_importer/management/commands/import_md.py 2016-01-19 00:21:38 +0000 | |||
2000 | @@ -1,300 +1,9 @@ | |||
2001 | 1 | import logging | ||
2002 | 2 | |||
2003 | 1 | from django.core.management.base import BaseCommand | 3 | from django.core.management.base import BaseCommand |
2300 | 2 | from django.core.management import call_command | 4 | |
2301 | 3 | from django.db import transaction | 5 | from md_importer.importer.process import process_branch |
2302 | 4 | 6 | from md_importer.models import ExternalDocsBranch | |
2007 | 5 | from cms.api import create_page, add_plugin | ||
2008 | 6 | from cms.models import Page, Title | ||
2009 | 7 | from cms.utils import page_resolver | ||
2010 | 8 | |||
2011 | 9 | from bs4 import BeautifulSoup | ||
2012 | 10 | import codecs | ||
2013 | 11 | import glob | ||
2014 | 12 | import logging | ||
2015 | 13 | import markdown | ||
2016 | 14 | import os | ||
2017 | 15 | import re | ||
2018 | 16 | import shutil | ||
2019 | 17 | import subprocess | ||
2020 | 18 | import sys | ||
2021 | 19 | import tempfile | ||
2022 | 20 | |||
2023 | 21 | from developer_portal.models import ExternalDocsBranch | ||
2024 | 22 | |||
2025 | 23 | DOCS_DIRNAME = 'docs' | ||
2026 | 24 | |||
2027 | 25 | |||
2028 | 26 | class DBActions: | ||
2029 | 27 | added_pages = [] | ||
2030 | 28 | removed_pages = [] | ||
2031 | 29 | |||
2032 | 30 | def add_page(self, **kwargs): | ||
2033 | 31 | self.added_pages += [kwargs] | ||
2034 | 32 | |||
2035 | 33 | def remove_page(self, page_id): | ||
2036 | 34 | self.removed_pages += [page_id] | ||
2037 | 35 | |||
2038 | 36 | @transaction.commit_on_success() | ||
2039 | 37 | def run(self): | ||
2040 | 38 | for added_page in self.added_pages: | ||
2041 | 39 | page = get_or_create_page(**added_page) | ||
2042 | 40 | page.publish('en') | ||
2043 | 41 | |||
2044 | 42 | # Only remove pages created by a script! | ||
2045 | 43 | Page.objects.filter(id__in=self.removed_pages, | ||
2046 | 44 | created_by="script").delete() | ||
2047 | 45 | |||
2048 | 46 | # https://stackoverflow.com/questions/33284171/ | ||
2049 | 47 | call_command('cms', 'fix-mptt') | ||
2050 | 48 | |||
2051 | 49 | |||
2052 | 50 | class MarkdownFile: | ||
2053 | 51 | html = None | ||
2054 | 52 | |||
2055 | 53 | def __init__(self, fn, docs_namespace, db_actions, slug_override=None): | ||
2056 | 54 | self.fn = fn | ||
2057 | 55 | self.docs_namespace = docs_namespace | ||
2058 | 56 | self.db_actions = db_actions | ||
2059 | 57 | if slug_override: | ||
2060 | 58 | self.slug = slug_override | ||
2061 | 59 | else: | ||
2062 | 60 | self.slug = slugify(self.fn) | ||
2063 | 61 | self.full_url = os.path.join(self.docs_namespace, self.slug) | ||
2064 | 62 | with codecs.open(self.fn, 'r', encoding='utf-8') as f: | ||
2065 | 63 | self.html = markdown.markdown( | ||
2066 | 64 | f.read(), | ||
2067 | 65 | output_format="html5", | ||
2068 | 66 | extensions=['markdown.extensions.tables']) | ||
2069 | 67 | self.release_alias = self._get_release_alias() | ||
2070 | 68 | self.title = self._read_title() | ||
2071 | 69 | self._remove_body_and_html_tags() | ||
2072 | 70 | self._use_developer_site_style() | ||
2073 | 71 | |||
2074 | 72 | def _get_release_alias(self): | ||
2075 | 73 | alias = re.findall(r'/tmp/tmp\S+?/(\S+?)/%s/\S+?' % DOCS_DIRNAME, | ||
2076 | 74 | self.fn) | ||
2077 | 75 | return alias[0] | ||
2078 | 76 | |||
2079 | 77 | def _read_title(self): | ||
2080 | 78 | soup = BeautifulSoup(self.html, 'html5lib') | ||
2081 | 79 | if soup.title: | ||
2082 | 80 | return soup.title.text | ||
2083 | 81 | if soup.h1: | ||
2084 | 82 | return soup.h1.text | ||
2085 | 83 | return slugify(self.fn).replace('-', ' ').title() | ||
2086 | 84 | |||
2087 | 85 | def _remove_body_and_html_tags(self): | ||
2088 | 86 | self.html = re.sub(r"<html>\n\s<body>\n", "", self.html, | ||
2089 | 87 | flags=re.MULTILINE) | ||
2090 | 88 | self.html = re.sub(r"\s<\/body>\n<\/html>", "", self.html, | ||
2091 | 89 | flags=re.MULTILINE) | ||
2092 | 90 | |||
2093 | 91 | def _use_developer_site_style(self): | ||
2094 | 92 | begin = (u"<div class=\"row no-border\">" | ||
2095 | 93 | "\n<div class=\"eight-col\">\n") | ||
2096 | 94 | end = u"</div>\n</div>" | ||
2097 | 95 | self.html = begin + self.html + end | ||
2098 | 96 | self.html = self.html.replace( | ||
2099 | 97 | "<pre><code>", | ||
2100 | 98 | "</div><div class=\"twelve-col\"><pre><code>") | ||
2101 | 99 | self.html = self.html.replace( | ||
2102 | 100 | "</code></pre>", | ||
2103 | 101 | "</code></pre></div><div class=\"eight-col\">") | ||
2104 | 102 | |||
2105 | 103 | def replace_links(self, titles, url_map): | ||
2106 | 104 | for title in titles: | ||
2107 | 105 | local_md_fn = os.path.basename(title) | ||
2108 | 106 | url = u'/'+url_map[title] | ||
2109 | 107 | # Replace links of the form <a href="/path/somefile.md"> first | ||
2110 | 108 | href = u"<a href=\"{}\">".format(url) | ||
2111 | 109 | md_href = u"<a href=\"{}\">".format(local_md_fn) | ||
2112 | 110 | self.html = self.html.replace(md_href, href) | ||
2113 | 111 | |||
2114 | 112 | # Now we can replace free-standing "somefile.md" references in | ||
2115 | 113 | # the HTML | ||
2116 | 114 | link = href + u"{}</a>".format(titles[title]) | ||
2117 | 115 | self.html = self.html.replace(local_md_fn, link) | ||
2118 | 116 | |||
2119 | 117 | def publish(self): | ||
2120 | 118 | '''Publishes pages in their branch alias namespace.''' | ||
2121 | 119 | self.db_actions.add_page( | ||
2122 | 120 | title=self.title, full_url=self.full_url, menu_title=self.title, | ||
2123 | 121 | html=self.html) | ||
2124 | 122 | |||
2125 | 123 | |||
2126 | 124 | class SnappyMarkdownFile(MarkdownFile): | ||
2127 | 125 | def __init__(self, fn, docs_namespace, db_actions): | ||
2128 | 126 | MarkdownFile.__init__(self, fn, docs_namespace, db_actions) | ||
2129 | 127 | self._make_snappy_mods() | ||
2130 | 128 | |||
2131 | 129 | def _make_snappy_mods(self): | ||
2132 | 130 | # Make sure the reader knows which documentation she is browsing | ||
2133 | 131 | if self.release_alias != 'current': | ||
2134 | 132 | before = (u"<div class=\"row no-border\">\n" | ||
2135 | 133 | "<div class=\"eight-col\">\n") | ||
2136 | 134 | after = (u"<div class=\"row no-border\">\n" | ||
2137 | 135 | "<div class=\"box pull-three three-col\">" | ||
2138 | 136 | "<p>You are browsing the Snappy <code>%s</code> " | ||
2139 | 137 | "documentation.</p>" | ||
2140 | 138 | "<p><a href=\"/snappy/guides/current/%s\">" | ||
2141 | 139 | "Back to the latest stable release ›" | ||
2142 | 140 | "</a></p></div>\n" | ||
2143 | 141 | "<div class=\"eight-col\">\n") % (self.release_alias, | ||
2144 | 142 | self.slug, ) | ||
2145 | 143 | self.html = self.html.replace(before, after) | ||
2146 | 144 | |||
2147 | 145 | def publish(self): | ||
2148 | 146 | if self.release_alias == "current": | ||
2149 | 147 | # Add a guides/<page> redirect to guides/current/<page> | ||
2150 | 148 | self.db_actions.add_page( | ||
2151 | 149 | title=self.title, | ||
2152 | 150 | full_url=self.full_url.replace('/current', ''), | ||
2153 | 151 | redirect="/snappy/guides/current/%s" % (self.slug)) | ||
2154 | 152 | else: | ||
2155 | 153 | self.title += " (%s)" % (self.release_alias,) | ||
2156 | 154 | MarkdownFile.publish(self) | ||
2157 | 155 | |||
2158 | 156 | |||
2159 | 157 | def slugify(filename): | ||
2160 | 158 | return os.path.basename(filename).replace('.md', '') | ||
2161 | 159 | |||
2162 | 160 | |||
2163 | 161 | class LocalBranch: | ||
2164 | 162 | titles = {} | ||
2165 | 163 | url_map = {} | ||
2166 | 164 | |||
2167 | 165 | def __init__(self, dirname, external_branch, db_actions): | ||
2168 | 166 | self.dirname = dirname | ||
2169 | 167 | self.docs_path = os.path.join(self.dirname, DOCS_DIRNAME) | ||
2170 | 168 | self.doc_fns = glob.glob(self.docs_path+'/*.md') | ||
2171 | 169 | self.md_files = [] | ||
2172 | 170 | self.external_branch = external_branch | ||
2173 | 171 | self.docs_namespace = self.external_branch.docs_namespace | ||
2174 | 172 | self.release_alias = os.path.basename(self.docs_namespace) | ||
2175 | 173 | self.index_doc_title = self.release_alias.title() | ||
2176 | 174 | self.index_doc = self.external_branch.index_doc | ||
2177 | 175 | self.db_actions = db_actions | ||
2178 | 176 | self.markdown_class = MarkdownFile | ||
2179 | 177 | |||
2180 | 178 | def import_markdown(self): | ||
2181 | 179 | for doc_fn in self.doc_fns: | ||
2182 | 180 | if self.index_doc and os.path.basename(doc_fn) == self.index_doc: | ||
2183 | 181 | md_file = self.markdown_class( | ||
2184 | 182 | doc_fn, | ||
2185 | 183 | os.path.dirname(self.docs_namespace), | ||
2186 | 184 | self.db_actions, | ||
2187 | 185 | slug_override=os.path.basename(self.docs_namespace)) | ||
2188 | 186 | self.md_files.insert(0, md_file) | ||
2189 | 187 | else: | ||
2190 | 188 | md_file = self.markdown_class(doc_fn, self.docs_namespace, | ||
2191 | 189 | self.db_actions) | ||
2192 | 190 | self.md_files += [md_file] | ||
2193 | 191 | self.titles[md_file.fn] = md_file.title | ||
2194 | 192 | self.url_map[md_file.fn] = md_file.full_url | ||
2195 | 193 | if not self.index_doc: | ||
2196 | 194 | self._create_fake_index_doc() | ||
2197 | 195 | for md_file in self.md_files: | ||
2198 | 196 | md_file.replace_links(self.titles, self.url_map) | ||
2199 | 197 | |||
2200 | 198 | def remove_old_pages(self): | ||
2201 | 199 | imported_page_urls = set([md_file.full_url | ||
2202 | 200 | for md_file in self.md_files]) | ||
2203 | 201 | index_doc = page_resolver.get_page_queryset_from_path( | ||
2204 | 202 | self.docs_namespace) | ||
2205 | 203 | db_pages = [] | ||
2206 | 204 | if len(index_doc): | ||
2207 | 205 | # All pages in this namespace currently in the database | ||
2208 | 206 | db_pages = index_doc[0].get_descendants().all() | ||
2209 | 207 | for db_page in db_pages: | ||
2210 | 208 | still_relevant = False | ||
2211 | 209 | for url in imported_page_urls: | ||
2212 | 210 | if url in db_page.get_absolute_url(): | ||
2213 | 211 | still_relevant = True | ||
2214 | 212 | break | ||
2215 | 213 | # At this point we know that there's no match and the page | ||
2216 | 214 | # can be deleted. | ||
2217 | 215 | if not still_relevant: | ||
2218 | 216 | self.db_actions.remove_page(db_page.id) | ||
2219 | 217 | |||
2220 | 218 | def publish(self): | ||
2221 | 219 | for md_file in self.md_files: | ||
2222 | 220 | md_file.publish() | ||
2223 | 221 | |||
2224 | 222 | def _create_fake_index_doc(self): | ||
2225 | 223 | '''Creates a fake index page at the top of the branches | ||
2226 | 224 | docs namespace.''' | ||
2227 | 225 | |||
2228 | 226 | if self.docs_namespace == "current": | ||
2229 | 227 | redirect = "/snappy/guides" | ||
2230 | 228 | else: | ||
2231 | 229 | redirect = None | ||
2232 | 230 | |||
2233 | 231 | in_navigation = False | ||
2234 | 232 | menu_title = None | ||
2235 | 233 | list_pages = "" | ||
2236 | 234 | for page in self.md_files: | ||
2237 | 235 | list_pages += "<li><a href=\"%s\">%s</a></li>" \ | ||
2238 | 236 | % (os.path.basename(page.full_url), page.title) | ||
2239 | 237 | landing = ( | ||
2240 | 238 | u"<div class=\"row\"><div class=\"eight-col\">\n" | ||
2241 | 239 | "<p>This section contains documentation for the " | ||
2242 | 240 | "<code>%s</code> Snappy branch.</p>" | ||
2243 | 241 | "<p><ul class=\"list-ubuntu\">%s</ul></p>\n" | ||
2244 | 242 | "<p>Auto-imported from <a " | ||
2245 | 243 | "href=\"https://github.com/ubuntu-core/snappy\">%s</a>.</p>\n" | ||
2246 | 244 | "</div></div>") % (self.release_alias, list_pages, | ||
2247 | 245 | self.external_branch.lp_origin) | ||
2248 | 246 | self.db_actions.add_page( | ||
2249 | 247 | title=self.index_doc_title, full_url=self.docs_namespace, | ||
2250 | 248 | in_navigation=in_navigation, redirect=redirect, html=landing, | ||
2251 | 249 | menu_title=menu_title) | ||
2252 | 250 | |||
2253 | 251 | |||
2254 | 252 | class SnappyLocalBranch(LocalBranch): | ||
2255 | 253 | def __init__(self, dirname, external_branch, db_actions): | ||
2256 | 254 | LocalBranch.__init__(self, dirname, external_branch, db_actions) | ||
2257 | 255 | self.markdown_class = SnappyMarkdownFile | ||
2258 | 256 | self.index_doc_title = 'Snappy documentation' | ||
2259 | 257 | if self.release_alias != 'current': | ||
2260 | 258 | self.index_doc_title += ' (%s)' % self.release_alias | ||
2261 | 259 | |||
2262 | 260 | |||
2263 | 261 | def get_or_create_page(title, full_url, menu_title=None, | ||
2264 | 262 | in_navigation=True, redirect=None, html=None): | ||
2265 | 263 | # First check if pages already exist. | ||
2266 | 264 | pages = Title.objects.select_related('page').filter(path__regex=full_url) | ||
2267 | 265 | if pages: | ||
2268 | 266 | page = pages[0].page | ||
2269 | 267 | page.title = title | ||
2270 | 268 | page.publisher_is_draft = True | ||
2271 | 269 | page.menu_title = menu_title | ||
2272 | 270 | page.in_navigation = in_navigation | ||
2273 | 271 | page.redirect = redirect | ||
2274 | 272 | if html: | ||
2275 | 273 | # We create the page, so we know there's just one placeholder | ||
2276 | 274 | placeholder = page.placeholders.all()[0] | ||
2277 | 275 | if placeholder.get_plugins(): | ||
2278 | 276 | plugin = placeholder.get_plugins()[0].get_plugin_instance()[0] | ||
2279 | 277 | plugin.body = html | ||
2280 | 278 | plugin.save() | ||
2281 | 279 | else: | ||
2282 | 280 | add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html) | ||
2283 | 281 | else: | ||
2284 | 282 | parent_pages = Title.objects.select_related('page').filter( | ||
2285 | 283 | path__regex=os.path.dirname(full_url)) | ||
2286 | 284 | if not parent_pages: | ||
2287 | 285 | print('Parent %s not found.' % os.path.dirname(full_url)) | ||
2288 | 286 | sys.exit(1) | ||
2289 | 287 | parent = parent_pages[0].page | ||
2290 | 288 | |||
2291 | 289 | slug = os.path.basename(full_url) | ||
2292 | 290 | page = create_page( | ||
2293 | 291 | title, "default.html", "en", slug=slug, parent=parent, | ||
2294 | 292 | menu_title=menu_title, in_navigation=in_navigation, | ||
2295 | 293 | position="last-child", redirect=redirect) | ||
2296 | 294 | if html: | ||
2297 | 295 | placeholder = page.placeholders.get() | ||
2298 | 296 | add_plugin(placeholder, 'RawHtmlPlugin', 'en', body=html) | ||
2299 | 297 | return page | ||
2303 | 298 | 7 | ||
2304 | 299 | 8 | ||
2305 | 300 | def import_branches(selection): | 9 | def import_branches(selection): |
2306 | @@ -302,64 +11,26 @@ | |||
2307 | 302 | logging.error('No branches registered in the ' | 11 | logging.error('No branches registered in the ' |
2308 | 303 | 'ExternalDocsBranch table yet.') | 12 | 'ExternalDocsBranch table yet.') |
2309 | 304 | return | 13 | return |
2310 | 305 | tempdir = tempfile.mkdtemp() | ||
2311 | 306 | db_actions = DBActions() | ||
2312 | 307 | for branch in ExternalDocsBranch.objects.filter( | 14 | for branch in ExternalDocsBranch.objects.filter( |
2322 | 308 | docs_namespace__regex=selection): | 15 | origin__regex=selection, active=True): |
2323 | 309 | checkout_location = os.path.join( | 16 | if not process_branch(branch): |
2315 | 310 | tempdir, os.path.basename(branch.docs_namespace)) | ||
2316 | 311 | sourcecode = SourceCode(branch.lp_origin, checkout_location) | ||
2317 | 312 | if sourcecode.get() != 0: | ||
2318 | 313 | logging.error( | ||
2319 | 314 | 'Could not check out branch "%s".' % branch.lp_origin) | ||
2320 | 315 | if os.path.exists(checkout_location): | ||
2321 | 316 | shutil.rmtree(checkout_location) | ||
2324 | 317 | break | 17 | break |
2325 | 318 | if branch.lp_origin.startswith('lp:snappy') or \ | ||
2326 | 319 | 'snappy' in branch.lp_origin.split(':')[1].split('.git')[0].split('/'): | ||
2327 | 320 | local_branch = SnappyLocalBranch(checkout_location, branch, | ||
2328 | 321 | db_actions) | ||
2329 | 322 | else: | ||
2330 | 323 | local_branch = LocalBranch(checkout_location, branch, db_actions) | ||
2331 | 324 | local_branch.import_markdown() | ||
2332 | 325 | local_branch.publish() | ||
2333 | 326 | local_branch.remove_old_pages() | ||
2334 | 327 | shutil.rmtree(tempdir) | ||
2335 | 328 | db_actions.run() | ||
2336 | 329 | |||
2337 | 330 | |||
2338 | 331 | class SourceCode(): | ||
2339 | 332 | def __init__(self, branch_origin, checkout_location): | ||
2340 | 333 | self.branch_origin = branch_origin | ||
2341 | 334 | self.checkout_location = checkout_location | ||
2342 | 335 | |||
2343 | 336 | def get(self): | ||
2344 | 337 | if self.branch_origin.startswith('lp:') and \ | ||
2345 | 338 | os.path.exists('/usr/bin/bzr'): | ||
2346 | 339 | return subprocess.call([ | ||
2347 | 340 | 'bzr', 'checkout', '--lightweight', self.branch_origin, | ||
2348 | 341 | self.checkout_location]) | ||
2349 | 342 | if self.branch_origin.startswith('https://github.com') and \ | ||
2350 | 343 | self.branch_origin.endswith('.git') and \ | ||
2351 | 344 | os.path.exists('/usr/bin/git'): | ||
2352 | 345 | return subprocess.call([ | ||
2353 | 346 | 'git', 'clone', '-q', self.branch_origin, | ||
2354 | 347 | self.checkout_location]) | ||
2355 | 348 | logging.error( | ||
2356 | 349 | 'Branch format "{}" not understood.'.format(self.branch_origin)) | ||
2357 | 350 | return 1 | ||
2358 | 351 | 18 | ||
2359 | 352 | 19 | ||
2360 | 353 | class Command(BaseCommand): | 20 | class Command(BaseCommand): |
2361 | 354 | help = "Import external branches for documentation." | 21 | help = "Import external branches for documentation." |
2362 | 355 | 22 | ||
2363 | 23 | def add_arguments(self, parser): | ||
2364 | 24 | parser.add_argument('branches', nargs='*') | ||
2365 | 25 | |||
2366 | 356 | def handle(*args, **options): | 26 | def handle(*args, **options): |
2367 | 357 | logging.basicConfig( | 27 | logging.basicConfig( |
2368 | 358 | level=logging.ERROR, | 28 | level=logging.ERROR, |
2369 | 359 | format='%(asctime)s %(levelname)-8s %(message)s', | 29 | format='%(asctime)s %(levelname)-8s %(message)s', |
2370 | 360 | datefmt='%F %T') | 30 | datefmt='%F %T') |
2373 | 361 | if len(args) < 2 or args[1] == "all": | 31 | branches = options['branches'] |
2374 | 362 | selection = '.*' | 32 | if not branches: |
2375 | 33 | import_branches('.*') | ||
2376 | 363 | else: | 34 | else: |
2379 | 364 | selection = args[1] | 35 | for b in branches: |
2380 | 365 | import_branches(selection) | 36 | import_branches(b) |
2381 | 366 | 37 | ||
2382 | === added directory 'md_importer/migrations' | |||
2383 | === added file 'md_importer/migrations/0001_initial.py' | |||
2384 | --- md_importer/migrations/0001_initial.py 1970-01-01 00:00:00 +0000 | |||
2385 | +++ md_importer/migrations/0001_initial.py 2016-01-19 00:21:38 +0000 | |||
2386 | @@ -0,0 +1,46 @@ | |||
2387 | 1 | # -*- coding: utf-8 -*- | ||
2388 | 2 | from __future__ import unicode_literals | ||
2389 | 3 | |||
2390 | 4 | from django.db import migrations, models | ||
2391 | 5 | |||
2392 | 6 | |||
2393 | 7 | class Migration(migrations.Migration): | ||
2394 | 8 | |||
2395 | 9 | dependencies = [ | ||
2396 | 10 | ('cms', '0013_urlconfrevision'), | ||
2397 | 11 | ] | ||
2398 | 12 | |||
2399 | 13 | operations = [ | ||
2400 | 14 | migrations.CreateModel( | ||
2401 | 15 | name='ExternalDocsBranch', | ||
2402 | 16 | fields=[ | ||
2403 | 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | ||
2404 | 18 | ('origin', models.CharField(help_text='External branch location, ie: lp:snappy/15.04 or https://github.com/ubuntu-core/snappy.git', max_length=200)), | ||
2405 | 19 | ('branch_name', models.CharField(help_text='For use with git branches, ie: "master" or "15.04" or "1.x".', max_length=200, blank=True)), | ||
2406 | 20 | ('post_checkout_command', models.CharField(help_text='Command to run after checkout of the branch.', max_length=100, blank=True)), | ||
2407 | 21 | ('active', models.BooleanField(default=True)), | ||
2408 | 22 | ], | ||
2409 | 23 | options={ | ||
2410 | 24 | 'verbose_name': 'external docs branch', | ||
2411 | 25 | 'verbose_name_plural': 'external docs branches', | ||
2412 | 26 | }, | ||
2413 | 27 | ), | ||
2414 | 28 | migrations.CreateModel( | ||
2415 | 29 | name='ExternalDocsBranchImportDirective', | ||
2416 | 30 | fields=[ | ||
2417 | 31 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | ||
2418 | 32 | ('import_from', models.CharField(help_text='File or directory to import from the branch. Ie: "docs/intro.md" (file) or "docs" (complete directory), etc.', max_length=150, blank=True)), | ||
2419 | 33 | ('write_to', models.CharField(help_text='Article URL (for a specific file) or article namespace for a directory or a set of files.', max_length=150, blank=True)), | ||
2420 | 34 | ('external_docs_branch', models.ForeignKey(to='md_importer.ExternalDocsBranch')), | ||
2421 | 35 | ], | ||
2422 | 36 | ), | ||
2423 | 37 | migrations.CreateModel( | ||
2424 | 38 | name='ImportedArticle', | ||
2425 | 39 | fields=[ | ||
2426 | 40 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | ||
2427 | 41 | ('last_import', models.DateTimeField(help_text='Datetime of last import.', verbose_name='Datetime')), | ||
2428 | 42 | ('branch', models.ForeignKey(to='md_importer.ExternalDocsBranch')), | ||
2429 | 43 | ('page', models.ForeignKey(to='cms.Page')), | ||
2430 | 44 | ], | ||
2431 | 45 | ), | ||
2432 | 46 | ] | ||
2433 | 0 | 47 | ||
2434 | === added file 'md_importer/migrations/__init__.py' | |||
2435 | === added file 'md_importer/models.py' | |||
2436 | --- md_importer/models.py 1970-01-01 00:00:00 +0000 | |||
2437 | +++ md_importer/models.py 2016-01-19 00:21:38 +0000 | |||
2438 | @@ -0,0 +1,56 @@ | |||
2439 | 1 | from django.db import models | ||
2440 | 2 | from django.utils.translation import ugettext_lazy as _ | ||
2441 | 3 | |||
2442 | 4 | from cms.models import Page | ||
2443 | 5 | |||
2444 | 6 | |||
2445 | 7 | class ExternalDocsBranch(models.Model): | ||
2446 | 8 | origin = models.CharField( | ||
2447 | 9 | max_length=200, | ||
2448 | 10 | help_text=_('External branch location, ie: lp:snappy/15.04 or ' | ||
2449 | 11 | 'https://github.com/ubuntu-core/snappy.git')) | ||
2450 | 12 | branch_name = models.CharField( | ||
2451 | 13 | max_length=200, | ||
2452 | 14 | help_text=_('For use with git branches, ie: "master" or "15.04" ' | ||
2453 | 15 | 'or "1.x".'), | ||
2454 | 16 | blank=True) | ||
2455 | 17 | post_checkout_command = models.CharField( | ||
2456 | 18 | max_length=100, | ||
2457 | 19 | help_text=_('Command to run after checkout of the branch.'), | ||
2458 | 20 | blank=True) | ||
2459 | 21 | active = models.BooleanField(default=True) | ||
2460 | 22 | |||
2461 | 23 | def __str__(self): | ||
2462 | 24 | if self.branch_name: | ||
2463 | 25 | return "{} - {}".format(self.origin, self.branch_name) | ||
2464 | 26 | return "{}".format(self.origin) | ||
2465 | 27 | |||
2466 | 28 | class Meta: | ||
2467 | 29 | verbose_name = "external docs branch" | ||
2468 | 30 | verbose_name_plural = "external docs branches" | ||
2469 | 31 | |||
2470 | 32 | |||
2471 | 33 | class ExternalDocsBranchImportDirective(models.Model): | ||
2472 | 34 | external_docs_branch = models.ForeignKey(ExternalDocsBranch) | ||
2473 | 35 | import_from = models.CharField( | ||
2474 | 36 | max_length=150, | ||
2475 | 37 | help_text=_('File or directory to import from the branch. ' | ||
2476 | 38 | 'Ie: "docs/intro.md" (file) or ' | ||
2477 | 39 | '"docs" (complete directory), etc.'), | ||
2478 | 40 | blank=True) | ||
2479 | 41 | write_to = models.CharField( | ||
2480 | 42 | max_length=150, | ||
2481 | 43 | help_text=_('Article URL (for a specific file) or article namespace ' | ||
2482 | 44 | 'for a directory or a set of files.'), | ||
2483 | 45 | blank=True) | ||
2484 | 46 | |||
2485 | 47 | def __str__(self): | ||
2486 | 48 | return "{} -- {}".format(self.external_docs_branch, | ||
2487 | 49 | self.import_from) | ||
2488 | 50 | |||
2489 | 51 | |||
2490 | 52 | class ImportedArticle(models.Model): | ||
2491 | 53 | page = models.ForeignKey(Page) | ||
2492 | 54 | branch = models.ForeignKey(ExternalDocsBranch) | ||
2493 | 55 | last_import = models.DateTimeField( | ||
2494 | 56 | _('Datetime'), help_text=_('Datetime of last import.')) | ||
2495 | 0 | 57 | ||
2496 | === added directory 'md_importer/tests' | |||
2497 | === added file 'md_importer/tests/__init__.py' | |||
2498 | === added directory 'md_importer/tests/data' | |||
2499 | === added directory 'md_importer/tests/data/link-broken-test' | |||
2500 | === added file 'md_importer/tests/data/link-broken-test/file1.md' | |||
2501 | --- md_importer/tests/data/link-broken-test/file1.md 1970-01-01 00:00:00 +0000 | |||
2502 | +++ md_importer/tests/data/link-broken-test/file1.md 2016-01-19 00:21:38 +0000 | |||
2503 | @@ -0,0 +1,5 @@ | |||
2504 | 1 | # Test | ||
2505 | 2 | |||
2506 | 3 | This is a link to [the file3 file](file3.md). | ||
2507 | 4 | |||
2508 | 5 | That's it. | ||
2509 | 0 | 6 | ||
2510 | === added file 'md_importer/tests/data/link-broken-test/file2.md' | |||
2511 | --- md_importer/tests/data/link-broken-test/file2.md 1970-01-01 00:00:00 +0000 | |||
2512 | +++ md_importer/tests/data/link-broken-test/file2.md 2016-01-19 00:21:38 +0000 | |||
2513 | @@ -0,0 +1,3 @@ | |||
2514 | 1 | # File 2 | ||
2515 | 2 | |||
2516 | 3 | Here's just some text. | ||
2517 | 0 | 4 | ||
2518 | === added directory 'md_importer/tests/data/link-test' | |||
2519 | === added file 'md_importer/tests/data/link-test/file1.md' | |||
2520 | --- md_importer/tests/data/link-test/file1.md 1970-01-01 00:00:00 +0000 | |||
2521 | +++ md_importer/tests/data/link-test/file1.md 2016-01-19 00:21:38 +0000 | |||
2522 | @@ -0,0 +1,5 @@ | |||
2523 | 1 | # Test | ||
2524 | 2 | |||
2525 | 3 | This is a link to [the file2 file](file2.md). | ||
2526 | 4 | |||
2527 | 5 | That's it. | ||
2528 | 0 | 6 | ||
2529 | === added file 'md_importer/tests/data/link-test/file2.md' | |||
2530 | --- md_importer/tests/data/link-test/file2.md 1970-01-01 00:00:00 +0000 | |||
2531 | +++ md_importer/tests/data/link-test/file2.md 2016-01-19 00:21:38 +0000 | |||
2532 | @@ -0,0 +1,3 @@ | |||
2533 | 1 | # File 2 | ||
2534 | 2 | |||
2535 | 3 | Here's just some text. | ||
2536 | 0 | 4 | ||
2537 | === added directory 'md_importer/tests/data/link2-test' | |||
2538 | === added file 'md_importer/tests/data/link2-test/file1.md' | |||
2539 | --- md_importer/tests/data/link2-test/file1.md 1970-01-01 00:00:00 +0000 | |||
2540 | +++ md_importer/tests/data/link2-test/file1.md 2016-01-19 00:21:38 +0000 | |||
2541 | @@ -0,0 +1,5 @@ | |||
2542 | 1 | # Test | ||
2543 | 2 | |||
2544 | 3 | This is a link to [the file3 file](file3.md). | ||
2545 | 4 | |||
2546 | 5 | That's it. | ||
2547 | 0 | 6 | ||
2548 | === added file 'md_importer/tests/data/link2-test/file3.md' | |||
2549 | --- md_importer/tests/data/link2-test/file3.md 1970-01-01 00:00:00 +0000 | |||
2550 | +++ md_importer/tests/data/link2-test/file3.md 2016-01-19 00:21:38 +0000 | |||
2551 | @@ -0,0 +1,3 @@ | |||
2552 | 1 | # File 3 | ||
2553 | 2 | |||
2554 | 3 | Here's just some text. | ||
2555 | 0 | 4 | ||
2556 | === added directory 'md_importer/tests/data/local-image-test' | |||
2557 | === added file 'md_importer/tests/data/local-image-test/test1.md' | |||
2558 | --- md_importer/tests/data/local-image-test/test1.md 1970-01-01 00:00:00 +0000 | |||
2559 | +++ md_importer/tests/data/local-image-test/test1.md 2016-01-19 00:21:38 +0000 | |||
2560 | @@ -0,0 +1,6 @@ | |||
2561 | 1 | # Local image test | ||
2562 | 2 | |||
2563 | 3 | Here is a local image: | ||
2564 | 4 | |||
2565 | 5 | ![Alt text for a remote image](img.jpg) | ||
2566 | 6 | |||
2567 | 0 | 7 | ||
2568 | === added directory 'md_importer/tests/data/remote-image-test' | |||
2569 | === added file 'md_importer/tests/data/remote-image-test/test1.md' | |||
2570 | --- md_importer/tests/data/remote-image-test/test1.md 1970-01-01 00:00:00 +0000 | |||
2571 | +++ md_importer/tests/data/remote-image-test/test1.md 2016-01-19 00:21:38 +0000 | |||
2572 | @@ -0,0 +1,6 @@ | |||
2573 | 1 | # Local image test | ||
2574 | 2 | |||
2575 | 3 | Here is a local image: | ||
2576 | 4 | |||
2577 | 5 | ![Alt text for a remote image](http://design.ubuntu.com/wp-content/uploads/ubuntu-logo112.png) | ||
2578 | 6 | |||
2579 | 0 | 7 | ||
2580 | === added directory 'md_importer/tests/data/snapcraft-test' | |||
2581 | === added file 'md_importer/tests/data/snapcraft-test/CONTRIBUTING.md' | |||
2582 | --- md_importer/tests/data/snapcraft-test/CONTRIBUTING.md 1970-01-01 00:00:00 +0000 | |||
2583 | +++ md_importer/tests/data/snapcraft-test/CONTRIBUTING.md 2016-01-19 00:21:38 +0000 | |||
2584 | @@ -0,0 +1,32 @@ | |||
2585 | 1 | # Snapcraft Contribution Guide | ||
2586 | 2 | |||
2587 | 3 | Welcome to Snapcraft! We're a pretty friendly community and we're thrilled that | ||
2588 | 4 | you want to make Snapcraft even better. However, we do ask that you follow some | ||
2589 | 5 | general guidelines while doing so, just so we can keep things organized around | ||
2590 | 6 | here. | ||
2591 | 7 | |||
2592 | 8 | 1. Sign the [contributor license agreement][1]. | ||
2593 | 9 | |||
2594 | 10 | This is how you give us permission to use your contributions. | ||
2595 | 11 | |||
2596 | 12 | 2. We use a forking, feature-based workflow. | ||
2597 | 13 | |||
2598 | 14 | Make a fork of Snapcraft, and create a branch named specifically for the | ||
2599 | 15 | feature on which you'd like to work. Make your changes there, adding new | ||
2600 | 16 | tests as needed, and make sure the existing tests continue to pass when your | ||
2601 | 17 | changes are complete (for information about running the tests, see the | ||
2602 | 18 | [HACKING][2] document). | ||
2603 | 19 | |||
2604 | 20 | 3. Squash commits into one, well-formatted commit. | ||
2605 | 21 | |||
2606 | 22 | If you really feel like there should be more than one commit in your branch, | ||
2607 | 23 | then you're probably trying to introduce more than one feature and you should | ||
2608 | 24 | make another branch for it. | ||
2609 | 25 | |||
2610 | 26 | 4. Submit a pull request to get changes from your branch into master. | ||
2611 | 27 | |||
2612 | 28 | If you want to get the change into 1.x as well, make a note of it on the pull | ||
2613 | 29 | request and it can be cherry-picked after the merge. | ||
2614 | 30 | |||
2615 | 31 | [1]: http://www.ubuntu.com/legal/contributors/ | ||
2616 | 32 | [2]: HACKING.md | ||
2617 | 0 | 33 | ||
2618 | === added file 'md_importer/tests/data/snapcraft-test/HACKING.md' | |||
2619 | --- md_importer/tests/data/snapcraft-test/HACKING.md 1970-01-01 00:00:00 +0000 | |||
2620 | +++ md_importer/tests/data/snapcraft-test/HACKING.md 2016-01-19 00:21:38 +0000 | |||
2621 | @@ -0,0 +1,48 @@ | |||
2622 | 1 | # Snapcraft | ||
2623 | 2 | |||
2624 | 3 | ## Running | ||
2625 | 4 | |||
2626 | 5 | To see all the commands and options, run `snapcraft --help`. | ||
2627 | 6 | |||
2628 | 7 | ## Testing | ||
2629 | 8 | |||
2630 | 9 | Simply run the top level testing script: | ||
2631 | 10 | |||
2632 | 11 | ./runtests.sh | ||
2633 | 12 | |||
2634 | 13 | - If you want to get a test coverage report, install python3-coverage before running the tests: | ||
2635 | 14 | |||
2636 | 15 | sudo apt-get install python3-coverage | ||
2637 | 16 | |||
2638 | 17 | |||
2639 | 18 | - If you don't want to run the plainbox integration tests, you can skip them by setting SNAPCRAFT_TESTS_SKIP_PLAINBOX=1 in your environment. | ||
2640 | 19 | |||
2641 | 20 | - If you are on 15.04 or earlier, you will need to run: | ||
2642 | 21 | |||
2643 | 22 | sudo add-apt-repository ppa:hardware-certification/public | ||
2644 | 23 | |||
2645 | 24 | ### PPA | ||
2646 | 25 | |||
2647 | 26 | You can install the daily build PPA by running: | ||
2648 | 27 | |||
2649 | 28 | sudo add-apt-repository ppa:snappy-dev/snapcraft-daily | ||
2650 | 29 | |||
2651 | 30 | ## Hacking | ||
2652 | 31 | |||
2653 | 32 | We'd love the help! | ||
2654 | 33 | |||
2655 | 34 | - Submit pull requests against [snapcraft](https://github.com/ubuntu-core/snapcraft/pulls) | ||
2656 | 35 | - Our mailing list is snappy-devel@lists.ubuntu.com | ||
2657 | 36 | - We can also be found on the #snappy IRC channel on Freenode | ||
2658 | 37 | |||
2659 | 38 | ### Project Layout | ||
2660 | 39 | |||
2661 | 40 | - **bin:** Holds the main snapcraft script. Putting this bin in your PATH or directly running scripts from it will find the rest of the source tree automatically. | ||
2662 | 41 | |||
2663 | 42 | - **examples:** Entering the subdirectories and running `../../bin/snapcraft snap` will generally yield interesting results. These examples will give you an idea of what snapcraft can do. These examples are not used during automated testing, they are simply for experimenting. | ||
2664 | 43 | |||
2665 | 44 | - **plugins:** Holds yaml metadata for the current snapcraft plugins. | ||
2666 | 45 | |||
2667 | 46 | - **tests:** Tests, obviously. `unit` holds Python unit tests and `plainbox` holds plainbox integration tests. | ||
2668 | 47 | |||
2669 | 48 | - **snapcraft:** The Python module that houses the core snapcraft logic. The `plugins` subdirectory holds the code for each plugin. | ||
2670 | 0 | 49 | ||
2671 | === added file 'md_importer/tests/data/snapcraft-test/README.md' | |||
2672 | --- md_importer/tests/data/snapcraft-test/README.md 1970-01-01 00:00:00 +0000 | |||
2673 | +++ md_importer/tests/data/snapcraft-test/README.md 2016-01-19 00:21:38 +0000 | |||
2674 | @@ -0,0 +1,33 @@ | |||
2675 | 1 | [![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] | ||
2676 | 2 | |||
2677 | 3 | # Snapcraft | ||
2678 | 4 | |||
2679 | 5 | Snapcraft is a delightful packaging tool | ||
2680 | 6 | |||
2681 | 7 | Snapcraft helps you assemble a whole project in a single tree out of | ||
2682 | 8 | many pieces. It can drive a very wide range of build and packaging systems, | ||
2683 | 9 | so that you can simply list all the upstream projects you want and have | ||
2684 | 10 | them built and installed together as a single tree. | ||
2685 | 11 | |||
2686 | 12 | ![Snapcraft Overview][overview-image] | ||
2687 | 13 | |||
2688 | 14 | For example, say you want to make a product that includes PyPI packages, | ||
2689 | 15 | Node.js packages from NPM, Java, and a bunch of daemons written in C and | ||
2690 | 16 | C++ that are built with autotools, snapcraft would make assembling the | ||
2691 | 17 | final tree very easy. | ||
2692 | 18 | |||
2693 | 19 | Snapcraft allows easy crafting of snap packages for the [snappy Ubuntu Core](http://ubuntu.com/snappy) | ||
2694 | 20 | transactional update system. | ||
2695 | 21 | |||
2696 | 22 | ## More Information | ||
2697 | 23 | |||
2698 | 24 | * [Introduction](docs/intro.md) to all the details about the concepts behind snapcraft. | ||
2699 | 25 | * [Hacking guide](HACKING.md) to contribute if you're interested in developing Snapcraft. | ||
2700 | 26 | |||
2701 | 27 | [travis-image]: https://travis-ci.org/ubuntu-core/snapcraft.svg?branch=master | ||
2702 | 28 | [travis-url]: https://travis-ci.org/ubuntu-core/snapcraft | ||
2703 | 29 | |||
2704 | 30 | [coveralls-image]: https://coveralls.io/repos/ubuntu-core/snapcraft/badge.svg?branch=master&service=github | ||
2705 | 31 | [coveralls-url]: https://coveralls.io/github/ubuntu-core/snapcraft?branch=master | ||
2706 | 32 | |||
2707 | 33 | [overview-image]: https://rawgit.com/ted-gould/snapcraft/snapcraft-overview-diagram/docs/snapcraft%20overview.svg | ||
2708 | 0 | 34 | ||
2709 | === added directory 'md_importer/tests/data/snapcraft-test/docs' | |||
2710 | === added file 'md_importer/tests/data/snapcraft-test/docs/get-started.md' | |||
2711 | --- md_importer/tests/data/snapcraft-test/docs/get-started.md 1970-01-01 00:00:00 +0000 | |||
2712 | +++ md_importer/tests/data/snapcraft-test/docs/get-started.md 2016-01-19 00:21:38 +0000 | |||
2713 | @@ -0,0 +1,47 @@ | |||
2714 | 1 | # Setting up your Ubuntu development host | ||
2715 | 2 | |||
2716 | 3 | Ubuntu is a great and convenient OS for developers. Snappy developer tools are | ||
2717 | 4 | readily available to enable app developers familiar with Ubuntu to port and | ||
2718 | 5 | write new software for a snappy-based system easily. | ||
2719 | 6 | |||
2720 | 7 | For app developers that want the latest stable tools to work on Snappy | ||
2721 | 8 | technology, we recommend to use the latest classic Ubuntu Long-Term Support | ||
2722 | 9 | (LTS) release as the host. At the time of writing this is Ubuntu 14.04 LTS. For | ||
2723 | 10 | those not using an Ubuntu machine (and you should), you can use a VM | ||
2724 | 11 | (VirtualBox, VMware, Vagrant) to execute your Ubuntu development host. | ||
2725 | 12 | |||
2726 | 13 | This version of snapcraft only works on Ubuntu 16.04 (Xenial Xerus), for | ||
2727 | 14 | previous versions of snapcraft, refer to the | ||
2728 | 15 | [1.x documentation](https://github.com/ubuntu-core/snapcraft/blob/1.x/docs/get-started.md). | ||
2729 | 16 | |||
2730 | 17 | Once your Ubuntu host system is up and running, you can then enable the | ||
2731 | 18 | snappy-tools PPA to get the latest tools to develop for Snappy. A PPA is a | ||
2732 | 19 | Personal Package Archive that developers can subscribe to install and get | ||
2733 | 20 | frequent updates of the software the archive contains. Open up a terminal with | ||
2734 | 21 | `Ctrl+Alt+T` and type the following command to add the snappy-tools PPA to | ||
2735 | 22 | your system: | ||
2736 | 23 | |||
2737 | 24 | $ sudo apt-add-repository ppa:snappy-dev/tools | ||
2738 | 25 | $ sudo apt update | ||
2739 | 26 | |||
2740 | 27 | After that, running the following command will install the `snappy-tools` | ||
2741 | 28 | package, which will in turn install the optimal selection of Snappy development | ||
2742 | 29 | software to your system. | ||
2743 | 30 | |||
2744 | 31 | $ sudo apt install snappy-tools | ||
2745 | 32 | |||
2746 | 33 | The snappy-tools PPA is officially supported by the Snappy Core team for | ||
2747 | 34 | Ubuntu LTS releases. In addition to it, we try to keep snappy-tools also | ||
2748 | 35 | conveniently available for the latest Ubuntu stable release as well as the | ||
2749 | 36 | current development release for those who prefer those as host. For a | ||
2750 | 37 | production environment however, we recommend using an Ubuntu LTS-based host. | ||
2751 | 38 | |||
2752 | 39 | This is the most important selection of tools you will get after installation: | ||
2753 | 40 | |||
2754 | 41 | snappy try - try snaps from a .snap, the [stage] or [snap] dir | ||
2755 | 42 | snappy-remote - run snappy operations on remote snappy target by IP | ||
2756 | 43 | snapcraft - the snap build tool for all snaps | ||
2757 | 44 | |||
2758 | 45 | # Next | ||
2759 | 46 | |||
2760 | 47 | How about putting together [your first snap](your-first-snap.md) now? | ||
2761 | 0 | 48 | ||
2762 | === added file 'md_importer/tests/data/snapcraft-test/docs/intro.md' | |||
2763 | --- md_importer/tests/data/snapcraft-test/docs/intro.md 1970-01-01 00:00:00 +0000 | |||
2764 | +++ md_importer/tests/data/snapcraft-test/docs/intro.md 2016-01-19 00:21:38 +0000 | |||
2765 | @@ -0,0 +1,104 @@ | |||
2766 | 1 | # Intro | ||
2767 | 2 | |||
2768 | 3 | Snapcraft is a build and packaging tool which helps you package your software | ||
2769 | 4 | as a snap. It makes it easy to incorporate components from different sources | ||
2770 | 5 | and build technologies or solutions. | ||
2771 | 6 | |||
2772 | 7 | # Key concepts | ||
2773 | 8 | |||
2774 | 9 | A `.snap` package for the Ubuntu Core system contains all its | ||
2775 | 10 | dependencies. This has a couple of advantages over traditional `deb` or | ||
2776 | 11 | `rpm` based dependency handling, the most important being that a | ||
2777 | 12 | developer can always be assured that there are no regressions triggered by | ||
2778 | 13 | changes to the system underneath their app. | ||
2779 | 14 | |||
2780 | 15 | Snapcraft makes bundling these dependencies easy by allowing you to | ||
2781 | 16 | specify them as "parts" in the `snapcraft.yaml` file. | ||
2782 | 17 | |||
2783 | 18 | # Snappy | ||
2784 | 19 | |||
2785 | 20 | Snappy Ubuntu Core is a new rendition of Ubuntu with transactional updates - a | ||
2786 | 21 | minimal server image with the same libraries as today's Ubuntu, but | ||
2787 | 22 | applications are provided through a simpler mechanism. | ||
2788 | 23 | |||
2789 | 24 | Snappy apps and Ubuntu Core itself can be upgraded atomically and rolled back | ||
2790 | 25 | if needed. Apps are also strictly confined and sandboxed to safeguard your | ||
2791 | 26 | data and system. | ||
2792 | 27 | |||
2793 | 28 | ## Parts | ||
2794 | 29 | |||
2795 | 30 | A central aspect of a snapcraft recipe is a "part". A part is a piece | ||
2796 | 31 | of software or data that the snap package requires to work or to | ||
2797 | 32 | build other parts. Each part is managed by a snapcraft plugin and parts | ||
2798 | 33 | are usually independent of each other. | ||
2799 | 34 | |||
2800 | 35 | ## Plugin | ||
2801 | 36 | |||
2802 | 37 | Each part has a `plugin` associated to it, this `plugin` provides the mechanism | ||
2803 | 38 | to handle it. Parts are driven through plugins, there are a variety of plugins | ||
2804 | 39 | already included for python 2 and 3, go, java, and cmake or autotools based | ||
2805 | 40 | projects. | ||
2806 | 41 | |||
2807 | 42 | ## Lifecycle | ||
2808 | 43 | |||
2809 | 44 | Each part goes through the following steps: | ||
2810 | 45 | |||
2811 | 46 | ### Pull | ||
2812 | 47 | |||
2813 | 48 | The first is that each part is pulled. This step will download | ||
2814 | 49 | content, e.g. checkout a git repository or download a binary component | ||
2815 | 50 | like the Java SDK. Snapcraft will create a `parts/` directory with | ||
2816 | 51 | sub-directories like `parts/part-name/src` for each part that contains | ||
2817 | 52 | the downloaded content. | ||
2818 | 53 | |||
2819 | 54 | ### Build | ||
2820 | 55 | |||
2821 | 56 | The next step is that each part is built in its `parts/part-name/build` | ||
2822 | 57 | directory and installs itself into `parts/part-name/install`. | ||
2823 | 58 | |||
2824 | 59 | ### Stage | ||
2825 | 60 | |||
2826 | 61 | After the build of each part the parts are combined into a single | ||
2827 | 62 | directory tree that is called the "staging area". It can be found | ||
2828 | 63 | under the `./stage` directory. | ||
2829 | 64 | |||
2830 | 65 | This `./stage` directory is useful for building outside code that isn't in the | ||
2831 | 66 | `snapcraft.yaml` recipe against the snap contents. For example, you might | ||
2832 | 67 | build a local project against the libraries in `./stage` by running `snapcraft | ||
2833 | 68 | shell make`. Though in general, you are encouraged to add even local | ||
2834 | 69 | projects to snapcraft.yaml with a local `source:` path. | ||
2835 | 70 | |||
2836 | 71 | For rapid iteration one can run `snappy try` against this directory to have it | ||
2837 | 72 | mounted in a `snappy` capable system. | ||
2838 | 73 | |||
2839 | 74 | ### Strip | ||
2840 | 75 | |||
2841 | 76 | The strip step moves the data into a `./snap` directory. It contains only | ||
2842 | 77 | the content that will be put into the final snap package, unlike the staging | ||
2843 | 78 | area which may include some development files not destined for your package. | ||
2844 | 79 | |||
2845 | 80 | The Snappy metadata information about your project will also now be placed in | ||
2846 | 81 | `./snap/meta`. Snapcraft takes care of generating all the meta-data Snappy | ||
2847 | 82 | expects. For a breakdown of what this is, have a look at our [Snappy developer | ||
2848 | 83 | reference](https://developer.ubuntu.com/snappy/guides/packaging-format-apps/). | ||
2849 | 84 | |||
2850 | 85 | This `./snap` directory is useful for inspecting what is going into your snap | ||
2851 | 86 | and to make any final post-processing on snapcraft's output. | ||
2852 | 87 | |||
2853 | 88 | For rapid iteration one can run `snappy try` against this directory to have it | ||
2854 | 89 | mounted in a `snappy` capable system. | ||
2855 | 90 | |||
2856 | 91 | ### Snap | ||
2857 | 92 | |||
2858 | 93 | The final step builds a snap package out of the `snap` directory. This `.snap` | ||
2859 | 94 | file can be uploaded to the Ubuntu Store and published directly to Snappy | ||
2860 | 95 | users. | ||
2861 | 96 | |||
2862 | 97 | This command can also be used with a directory argument for projects that | ||
2863 | 98 | are not following the snapcraft lifecycle but which follow the internal | ||
2864 | 99 | snap format. | ||
2865 | 100 | |||
2866 | 101 | # Next | ||
2867 | 102 | |||
2868 | 103 | After introducing the key concept of snapcraft it is probably a good | ||
2869 | 104 | time [get set up](get-started.md) to create your first snap with snapcraft. | ||
2870 | 0 | 105 | ||
2871 | === added file 'md_importer/tests/data/snapcraft-test/docs/ros-snap.md' | |||
2872 | --- md_importer/tests/data/snapcraft-test/docs/ros-snap.md 1970-01-01 00:00:00 +0000 | |||
2873 | +++ md_importer/tests/data/snapcraft-test/docs/ros-snap.md 2016-01-19 00:21:38 +0000 | |||
2874 | @@ -0,0 +1,366 @@ | |||
2875 | 1 | # Using Snappy with ROS | ||
2876 | 2 | |||
2877 | 3 | The [Robot Operating System][1] (ROS) is an open-source collection of tools and | ||
2878 | 4 | libraries meant to aid in the development of complex robotic systems. ROS is | ||
2879 | 5 | excellent at what it does, but there are a few things it doesn't do: | ||
2880 | 6 | |||
2881 | 7 | - Security: How does one prevent a rogue application from interfering with one's | ||
2882 | 8 | finely-honed ROS ecosystem? Confinement and access control isn't one of the | ||
2883 | 9 | many problems solved by ROS. | ||
2884 | 10 | |||
2885 | 11 | - Deployment: How does one deploy an entire ROS project in one step, without | ||
2886 | 12 | worrying about dependencies? | ||
2887 | 13 | |||
2888 | 14 | - Updating: How does one allow end-users to update the entire ROS project in one | ||
2889 | 15 | step, while retaining for the possibility of rolling back the change if | ||
2890 | 16 | the update goes sideways? | ||
2891 | 17 | |||
2892 | 18 | These are all problems solved by Snappy; the combination of the two is a perfect | ||
2893 | 19 | match for consumer robotic systems. | ||
2894 | 20 | |||
2895 | 21 | |||
2896 | 22 | ## Package your current ROS project as a .snap | ||
2897 | 23 | |||
2898 | 24 | Let's assume you already have a ROS project. It can be as simple or as | ||
2899 | 25 | complicated as you like, but for this example, our project will be made up of: | ||
2900 | 26 | |||
2901 | 27 | - A ROS package containing a C++ "talker." | ||
2902 | 28 | - A ROS package containing a Python "listener," as well as a launch file to | ||
2903 | 29 | bring both the talker and listener up at the same time with roscore. | ||
2904 | 30 | |||
2905 | 31 | Our objective will be to create a .snap containing these pieces and their | ||
2906 | 32 | dependencies. The easiest way to do that is with Snapcraft, using its Catkin | ||
2907 | 33 | plugin. But before we get to that, let's create our project. Prerequisites for | ||
2908 | 34 | this walkthrough: | ||
2909 | 35 | |||
2910 | 36 | - Installed Snapcraft (see [Getting Started][2]). | ||
2911 | 37 | - Installed/configured ROS Indigo (see the [Indigo installation tutorial][3]). | ||
2912 | 38 | - An empty ROS workspace (see the Catkin [workspace tutorial][4]). | ||
2913 | 39 | - General ROS experience (at least go through the [C++ pub/sub tutorial][5]). | ||
2914 | 40 | - General Snapcraft experience (read [Your first snap][6]). | ||
2915 | 41 | |||
2916 | 42 | |||
2917 | 43 | ### C++ "talker" | ||
2918 | 44 | |||
2919 | 45 | First, create the package: | ||
2920 | 46 | |||
2921 | 47 | $ catkin_create_pkg talker roscpp std_msgs | ||
2922 | 48 | |||
2923 | 49 | Now make sure the `package.xml` is setup correctly (comments removed for | ||
2924 | 50 | brevity): | ||
2925 | 51 | |||
2926 | 52 | ```xml | ||
2927 | 53 | <?xml version="1.0"?> | ||
2928 | 54 | <package> | ||
2929 | 55 | <name>talker</name> | ||
2930 | 56 | <version>0.0.0</version> | ||
2931 | 57 | <description>The talker package</description> | ||
2932 | 58 | <maintainer email="ubuntu@todo.todo">ubuntu</maintainer> | ||
2933 | 59 | <license>TODO</license> | ||
2934 | 60 | <buildtool_depend>catkin</buildtool_depend> | ||
2935 | 61 | <build_depend>roscpp</build_depend> | ||
2936 | 62 | <build_depend>std_msgs</build_depend> | ||
2937 | 63 | <run_depend>roscpp</run_depend> | ||
2938 | 64 | <run_depend>std_msgs</run_depend> | ||
2939 | 65 | </package> | ||
2940 | 66 | ``` | ||
2941 | 67 | |||
2942 | 68 | Also make sure the `CMakeLists.txt` is setup correctly. Importantly, make sure | ||
2943 | 69 | that you've specified install rules, since Snapcraft uses `make install`. | ||
2944 | 70 | Anything that doesn't have an install rule won't end up in the final .snap: | ||
2945 | 71 | |||
2946 | 72 | ```cmake | ||
2947 | 73 | cmake_minimum_required(VERSION 2.8.3) | ||
2948 | 74 | project(talker) | ||
2949 | 75 | |||
2950 | 76 | find_package(catkin REQUIRED COMPONENTS | ||
2951 | 77 | roscpp | ||
2952 | 78 | std_msgs | ||
2953 | 79 | ) | ||
2954 | 80 | |||
2955 | 81 | catkin_package() | ||
2956 | 82 | |||
2957 | 83 | include_directories(${catkin_INCLUDE_DIRS}) | ||
2958 | 84 | |||
2959 | 85 | add_executable(talker_node src/talker_node.cpp) | ||
2960 | 86 | |||
2961 | 87 | target_link_libraries(talker_node ${catkin_LIBRARIES}) | ||
2962 | 88 | |||
2963 | 89 | install(TARGETS talker_node | ||
2964 | 90 | ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} | ||
2965 | 91 | LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} | ||
2966 | 92 | RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} | ||
2967 | 93 | ) | ||
2968 | 94 | ``` | ||
2969 | 95 | |||
2970 | 96 | Finally, for `src/talker_node.cpp`, we have this: | ||
2971 | 97 | |||
2972 | 98 | ```cpp | ||
2973 | 99 | #include <sstream> | ||
2974 | 100 | |||
2975 | 101 | #include <ros/ros.h> | ||
2976 | 102 | #include <std_msgs/String.h> | ||
2977 | 103 | |||
2978 | 104 | int main(int argc, char **argv) | ||
2979 | 105 | { | ||
2980 | 106 | ros::init(argc, argv, "talker"); | ||
2981 | 107 | |||
2982 | 108 | ros::NodeHandle nodeHandle; | ||
2983 | 109 | |||
2984 | 110 | ros::Publisher publisher = nodeHandle.advertise<std_msgs::String>("chatter", 1); | ||
2985 | 111 | |||
2986 | 112 | ros::Rate loopRate(10); | ||
2987 | 113 | |||
2988 | 114 | int count = 0; | ||
2989 | 115 | while (ros::ok()) | ||
2990 | 116 | { | ||
2991 | 117 | std_msgs::String message; | ||
2992 | 118 | |||
2993 | 119 | std::stringstream stream; | ||
2994 | 120 | stream << "Hello world " << count++; | ||
2995 | 121 | message.data = stream.str(); | ||
2996 | 122 | |||
2997 | 123 | ROS_INFO("%s", message.data.c_str()); | ||
2998 | 124 | |||
2999 | 125 | publisher.publish(message); | ||
3000 | 126 | |||
3001 | 127 | ros::spinOnce(); | ||
3002 | 128 | |||
3003 | 129 | loopRate.sleep(); | ||
3004 | 130 | } | ||
3005 | 131 | |||
3006 | 132 | return 0; | ||
3007 | 133 | } | ||
3008 | 134 | ``` | ||
3009 | 135 | |||
3010 | 136 | The "talker" is now complete. | ||
3011 | 137 | |||
3012 | 138 | |||
3013 | 139 | ### The Python "listener" | ||
3014 | 140 | |||
3015 | 141 | First, create the package: | ||
3016 | 142 | |||
3017 | 143 | $ catkin_create_pkg listener rospy std_msgs | ||
3018 | 144 | |||
3019 | 145 | Now make sure the `package.xml` is setup correctly (comments removed for | ||
3020 | 146 | brevity): | ||
3021 | 147 | |||
3022 | 148 | ```xml | ||
3023 | 149 | <?xml version="1.0"?> | ||
3024 | 150 | <package> | ||
3025 | 151 | <name>listener</name> | ||
3026 | 152 | <version>0.0.0</version> | ||
3027 | 153 | <description>The listener package</description> | ||
3028 | 154 | <maintainer email="ubuntu@todo.todo">ubuntu</maintainer> | ||
3029 | 155 | <license>TODO</license> | ||
3030 | 156 | <buildtool_depend>catkin</buildtool_depend> | ||
3031 | 157 | <build_depend>rospy</build_depend> | ||
3032 | 158 | <build_depend>std_msgs</build_depend> | ||
3033 | 159 | <run_depend>rospy</run_depend> | ||
3034 | 160 | <run_depend>std_msgs</run_depend> | ||
3035 | 161 | </package> | ||
3036 | 162 | ``` | ||
3037 | 163 | |||
3038 | 164 | Also make sure the `CMakeLists.txt` is setup correctly. Again, it's important to remember the install rules: | ||
3039 | 165 | |||
3040 | 166 | ```cmake | ||
3041 | 167 | cmake_minimum_required(VERSION 2.8.3) | ||
3042 | 168 | project(listener) | ||
3043 | 169 | |||
3044 | 170 | find_package(catkin REQUIRED COMPONENTS | ||
3045 | 171 | rospy | ||
3046 | 172 | std_msgs | ||
3047 | 173 | ) | ||
3048 | 174 | |||
3049 | 175 | catkin_package() | ||
3050 | 176 | |||
3051 | 177 | install(PROGRAMS | ||
3052 | 178 | scripts/listener_node | ||
3053 | 179 | DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} | ||
3054 | 180 | ) | ||
3055 | 181 | |||
3056 | 182 | install(FILES | ||
3057 | 183 | talk_and_listen.launch | ||
3058 | 184 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} | ||
3059 | 185 | ) | ||
3060 | 186 | ``` | ||
3061 | 187 | |||
3062 | 188 | Now for `scripts/listener_node`, we have this: | ||
3063 | 189 | |||
3064 | 190 | ```python | ||
3065 | 191 | #!/usr/bin/env python | ||
3066 | 192 | |||
3067 | 193 | import rospy | ||
3068 | 194 | from std_msgs.msg import String | ||
3069 | 195 | |||
3070 | 196 | def callback(data): | ||
3071 | 197 | rospy.loginfo('I heard %s', data.data) | ||
3072 | 198 | |||
3073 | 199 | def listener(): | ||
3074 | 200 | rospy.init_node('listener') | ||
3075 | 201 | |||
3076 | 202 | rospy.Subscriber('babble', String, callback) | ||
3077 | 203 | |||
3078 | 204 | rospy.spin() | ||
3079 | 205 | |||
3080 | 206 | if __name__ == '__main__': | ||
3081 | 207 | listener() | ||
3082 | 208 | ``` | ||
3083 | 209 | |||
3084 | 210 | Make sure this script is executable: | ||
3085 | 211 | |||
3086 | 212 | $ chmod +x scripts/listener_node | ||
3087 | 213 | |||
3088 | 214 | Note that the listener subscribes to the `babble` topic, but the talker | ||
3089 | 215 | publishes on `chatter`. We need to make sure we account for that in | ||
3090 | 216 | `talk_and_listen.launch`: | ||
3091 | 217 | |||
3092 | 218 | ```xml | ||
3093 | 219 | <launch> | ||
3094 | 220 | <node name = "talker" pkg = "talker" type = "talker_node" | ||
3095 | 221 | output = "screen" /> | ||
3096 | 222 | |||
3097 | 223 | <node name = "listener" pkg = "listener" type = "listener_node" | ||
3098 | 224 | output = "screen"> | ||
3099 | 225 | <remap from = "babble" to = "chatter" /> | ||
3100 | 226 | </node> | ||
3101 | 227 | </launch> | ||
3102 | 228 | ``` | ||
3103 | 229 | |||
3104 | 230 | The "listener" is now complete. | ||
3105 | 231 | |||
3106 | 232 | |||
3107 | 233 | ### Verify functionality | ||
3108 | 234 | |||
3109 | 235 | You can verify that everything works by getting in the workspace root and | ||
3110 | 236 | running: | ||
3111 | 237 | |||
3112 | 238 | $ catkin_make | ||
3113 | 239 | $ catkin_make install | ||
3114 | 240 | $ roslaunch listener talk_and_listen.launch | ||
3115 | 241 | |||
3116 | 242 | You should see them communicating, looking something like this: | ||
3117 | 243 | |||
3118 | 244 | ... | ||
3119 | 245 | [INFO] [WallTime: 1449512660.316140] I heard Hello world 2 | ||
3120 | 246 | [ INFO] [1449512660.415941529]: Hello world 3 | ||
3121 | 247 | [INFO] [WallTime: 1449512660.416330] I heard Hello world 3 | ||
3122 | 248 | [ INFO] [1449512660.515954882]: Hello world 4 | ||
3123 | 249 | [INFO] [WallTime: 1449512660.516307] I heard Hello world 4 | ||
3124 | 250 | [ INFO] [1449512660.615954473]: Hello world 5 | ||
3125 | 251 | [INFO] [WallTime: 1449512660.616306] I heard Hello world 5 | ||
3126 | 252 | ... | ||
3127 | 253 | |||
3128 | 254 | ### Put it all in a .snap | ||
3129 | 255 | |||
3130 | 256 | Let's get back to our stated objective, which was to create a .snap containing | ||
3131 | 257 | these pieces and their dependencies. As mentioned, the easiest way to create a | ||
3132 | 258 | .snap with complex dependencies is to use Snapcraft. Snapcraft contains a plugin | ||
3133 | 259 | especially for Catkin, which makes creating a ROS .snap particularly easy. | ||
3134 | 260 | |||
3135 | 261 | We tell Snapcraft how to create the .snap via a file named `snapcraft.yaml`; | ||
3136 | 262 | let's create that file in the workspace root, containing the following: | ||
3137 | 263 | |||
3138 | 264 | ```yaml | ||
3139 | 265 | name: ros-talker-and-listener | ||
3140 | 266 | version: 1.0 | ||
3141 | 267 | summary: ROS Example | ||
3142 | 268 | description: Contains talker/listener ROS packages and a .launch file. | ||
3143 | 269 | |||
3144 | 270 | binaries: | ||
3145 | 271 | launch-project: | ||
3146 | 272 | exec: roslaunch listener talk_and_listen.launch | ||
3147 | 273 | |||
3148 | 274 | parts: | ||
3149 | 275 | foo: | ||
3150 | 276 | plugin: catkin | ||
3151 | 277 | source: . | ||
3152 | 278 | catkin-packages: | ||
3153 | 279 | - talker | ||
3154 | 280 | - listener | ||
3155 | 281 | stage-packages: | ||
3156 | 282 | - ros-indigo-ros-core | ||
3157 | 283 | ``` | ||
3158 | 284 | |||
3159 | 285 | Most of this file should look familiar to you if you've met the prerequisites, | ||
3160 | 286 | but let's focus on a few specific pieces. | ||
3161 | 287 | |||
3162 | 288 | ```yaml | ||
3163 | 289 | # ... | ||
3164 | 290 | binaries: | ||
3165 | 291 | launch-project: | ||
3166 | 292 | exec: roslaunch listener talk_and_listen.launch | ||
3167 | 293 | # ... | ||
3168 | 294 | ``` | ||
3169 | 295 | |||
3170 | 296 | Even though the `talker` and `listener` packages will be installed, they'll be | ||
3171 | 297 | within a confined ROS installation, so the user won't be able to simply call | ||
3172 | 298 | `rosrun` or `roslaunch`. Instead, you have control over how your .snap is used, | ||
3173 | 299 | and here we specify that we only want a single binary, called "launch-project", | ||
3174 | 300 | which results in the `roslaunch` call you see. If this seems confusing now, it | ||
3175 | 301 | will make more sense when we actually use it. | ||
3176 | 302 | |||
3177 | 303 | ```yaml | ||
3178 | 304 | # ... | ||
3179 | 305 | parts: | ||
3180 | 306 | foo: | ||
3181 | 307 | plugin: catkin | ||
3182 | 308 | source: . | ||
3183 | 309 | catkin-packages: | ||
3184 | 310 | - talker | ||
3185 | 311 | - listener | ||
3186 | 312 | stage-packages: | ||
3187 | 313 | - ros-indigo-ros-core | ||
3188 | 314 | # ... | ||
3189 | 315 | ``` | ||
3190 | 316 | |||
3191 | 317 | This is specifying that the .snap is made up of a single part, called "foo," | ||
3192 | 318 | which utilizes the Catkin plugin. It states that the workspace is in the same | ||
3193 | 319 | path as the `snapcraft.yaml`, and it specifies which ROS packages should be | ||
3194 | 320 | included in the .snap (`talker` and `listener`). Finally, and this is important, | ||
3195 | 321 | it specifies that the Ubuntu package containing `roscore` (ros-indigo-ros-core) | ||
3196 | 322 | should be installed into the .snap. | ||
3197 | 323 | |||
3198 | 324 | That last point is worth discussing. Currently, since .snaps cannot depend upon | ||
3199 | 325 | each other, any .snap that uses roscore must distribute roscore within it. We're | ||
3200 | 326 | working on some new features that will enable sharing roscore between snaps, but | ||
3201 | 327 | until then there are some limitations to keep in mind: | ||
3202 | 328 | |||
3203 | 329 | - `roscore` must be bundled into each .snap that requires it. | ||
3204 | 330 | - Snappy's port negotiation feature is still a work-in-progress, which means | ||
3205 | 331 | that only a single .snap that runs `roscore` can be installed at a time or | ||
3206 | 332 | they will fight for the same port. | ||
3207 | 333 | |||
3208 | 334 | Now that we understand the `snapcraft.yaml`, let's create the .snap! | ||
3209 | 335 | |||
3210 | 336 | $ snapcraft snap | ||
3211 | 337 | |||
3212 | 338 | This will take some time to pull down the dependencies etc., but in the end | ||
3213 | 339 | you'll have a .snap. | ||
3214 | 340 | |||
3215 | 341 | |||
3216 | 342 | ### Take the .snap for a test drive | ||
3217 | 343 | |||
3218 | 344 | You can transfer your newly-minted .snap to your Ubuntu Core machine and install | ||
3219 | 345 | it at the same time via `snappy-remote`, for example: | ||
3220 | 346 | |||
3221 | 347 | $ snappy-remote --url=ssh://<host>:<port> install \ | ||
3222 | 348 | ros-talker-and-listener_1.0_amd64.snap | ||
3223 | 349 | |||
3224 | 350 | Now on the Ubuntu Core machine, take a look in `/apps/bin/`, and you'll see the | ||
3225 | 351 | binary you requested, called `ros-talker-and-listener.launch-project`. Test it | ||
3226 | 352 | out: | ||
3227 | 353 | |||
3228 | 354 | $ ros-talker-and-listener.launch-project | ||
3229 | 355 | |||
3230 | 356 | And you should see the talker and listener communicating like before. As usual, | ||
3231 | 357 | ctrl+c will stop it. Note also that, since ROS is running in a confined | ||
3232 | 358 | environment, its log isn't in `$HOME/.ros` as usual, but in | ||
3233 | 359 | `$HOME/apps/ros-talker-and-listener.sideload/1.0/ros`. | ||
3234 | 360 | |||
3235 | 361 | [1]: http://www.ros.org/ | ||
3236 | 362 | [2]: get-started.md | ||
3237 | 363 | [3]: http://wiki.ros.org/indigo/Installation/Ubuntu | ||
3238 | 364 | [4]: http://wiki.ros.org/catkin/Tutorials/create_a_workspace | ||
3239 | 365 | [5]: http://wiki.ros.org/ROS/Tutorials/WritingPublisherSubscriber%28c%2B%2B%29 | ||
3240 | 366 | [6]: your-first-snap.md | ||
3241 | 0 | 367 | ||
3242 | === added file 'md_importer/tests/data/snapcraft-test/docs/snapcraft overview.svg' | |||
3243 | --- md_importer/tests/data/snapcraft-test/docs/snapcraft overview.svg 1970-01-01 00:00:00 +0000 | |||
3244 | +++ md_importer/tests/data/snapcraft-test/docs/snapcraft overview.svg 2016-01-19 00:21:38 +0000 | |||
3245 | @@ -0,0 +1,1199 @@ | |||
3246 | 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
3247 | 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||
3248 | 3 | |||
3249 | 4 | <svg | ||
3250 | 5 | xmlns:dc="http://purl.org/dc/elements/1.1/" | ||
3251 | 6 | xmlns:cc="http://creativecommons.org/ns#" | ||
3252 | 7 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||
3253 | 8 | xmlns:svg="http://www.w3.org/2000/svg" | ||
3254 | 9 | xmlns="http://www.w3.org/2000/svg" | ||
3255 | 10 | xmlns:xlink="http://www.w3.org/1999/xlink" | ||
3256 | 11 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
3257 | 12 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
3258 | 13 | width="629.42749" | ||
3259 | 14 | height="583.61816" | ||
3260 | 15 | viewBox="0 0 629.4275 583.61817" | ||
3261 | 16 | id="svg2" | ||
3262 | 17 | version="1.1" | ||
3263 | 18 | inkscape:version="0.91 r13725" | ||
3264 | 19 | sodipodi:docname="snapcraft overview.svg"> | ||
3265 | 20 | <defs | ||
3266 | 21 | id="defs4"> | ||
3267 | 22 | <marker | ||
3268 | 23 | inkscape:isstock="true" | ||
3269 | 24 | style="overflow:visible" | ||
3270 | 25 | id="marker9243" | ||
3271 | 26 | refX="0" | ||
3272 | 27 | refY="0" | ||
3273 | 28 | orient="auto" | ||
3274 | 29 | inkscape:stockid="Arrow2Mend"> | ||
3275 | 30 | <path | ||
3276 | 31 | inkscape:connector-curvature="0" | ||
3277 | 32 | transform="scale(-0.6,-0.6)" | ||
3278 | 33 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3279 | 34 | style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3280 | 35 | id="path9245" /> | ||
3281 | 36 | </marker> | ||
3282 | 37 | <marker | ||
3283 | 38 | inkscape:isstock="true" | ||
3284 | 39 | style="overflow:visible" | ||
3285 | 40 | id="marker9024" | ||
3286 | 41 | refX="0" | ||
3287 | 42 | refY="0" | ||
3288 | 43 | orient="auto" | ||
3289 | 44 | inkscape:stockid="Arrow2Mend"> | ||
3290 | 45 | <path | ||
3291 | 46 | inkscape:connector-curvature="0" | ||
3292 | 47 | transform="scale(-0.6,-0.6)" | ||
3293 | 48 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3294 | 49 | style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3295 | 50 | id="path9026" /> | ||
3296 | 51 | </marker> | ||
3297 | 52 | <marker | ||
3298 | 53 | inkscape:isstock="true" | ||
3299 | 54 | style="overflow:visible" | ||
3300 | 55 | id="marker8885" | ||
3301 | 56 | refX="0" | ||
3302 | 57 | refY="0" | ||
3303 | 58 | orient="auto" | ||
3304 | 59 | inkscape:stockid="Arrow2Mend"> | ||
3305 | 60 | <path | ||
3306 | 61 | inkscape:connector-curvature="0" | ||
3307 | 62 | transform="scale(-0.6,-0.6)" | ||
3308 | 63 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3309 | 64 | style="fill:#ffffff;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3310 | 65 | id="path8887" /> | ||
3311 | 66 | </marker> | ||
3312 | 67 | <linearGradient | ||
3313 | 68 | inkscape:collect="always" | ||
3314 | 69 | id="linearGradient7897"> | ||
3315 | 70 | <stop | ||
3316 | 71 | style="stop-color:#ffffff;stop-opacity:0" | ||
3317 | 72 | offset="0" | ||
3318 | 73 | id="stop7899" /> | ||
3319 | 74 | <stop | ||
3320 | 75 | style="stop-color:#ffffff;stop-opacity:1" | ||
3321 | 76 | offset="1" | ||
3322 | 77 | id="stop7901" /> | ||
3323 | 78 | </linearGradient> | ||
3324 | 79 | <marker | ||
3325 | 80 | inkscape:isstock="true" | ||
3326 | 81 | style="overflow:visible" | ||
3327 | 82 | id="marker7803" | ||
3328 | 83 | refX="0" | ||
3329 | 84 | refY="0" | ||
3330 | 85 | orient="auto" | ||
3331 | 86 | inkscape:stockid="Arrow2Mend"> | ||
3332 | 87 | <path | ||
3333 | 88 | inkscape:connector-curvature="0" | ||
3334 | 89 | transform="scale(-0.6,-0.6)" | ||
3335 | 90 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3336 | 91 | style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3337 | 92 | id="path7805" /> | ||
3338 | 93 | </marker> | ||
3339 | 94 | <marker | ||
3340 | 95 | inkscape:isstock="true" | ||
3341 | 96 | style="overflow:visible" | ||
3342 | 97 | id="marker5350" | ||
3343 | 98 | refX="0" | ||
3344 | 99 | refY="0" | ||
3345 | 100 | orient="auto" | ||
3346 | 101 | inkscape:stockid="Arrow2Mend"> | ||
3347 | 102 | <path | ||
3348 | 103 | transform="scale(-0.6,-0.6)" | ||
3349 | 104 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3350 | 105 | style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3351 | 106 | id="path5352" | ||
3352 | 107 | inkscape:connector-curvature="0" /> | ||
3353 | 108 | </marker> | ||
3354 | 109 | <marker | ||
3355 | 110 | inkscape:isstock="true" | ||
3356 | 111 | style="overflow:visible" | ||
3357 | 112 | id="marker5271" | ||
3358 | 113 | refX="0" | ||
3359 | 114 | refY="0" | ||
3360 | 115 | orient="auto" | ||
3361 | 116 | inkscape:stockid="Arrow2Mend"> | ||
3362 | 117 | <path | ||
3363 | 118 | transform="scale(-0.6,-0.6)" | ||
3364 | 119 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3365 | 120 | style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3366 | 121 | id="path5273" | ||
3367 | 122 | inkscape:connector-curvature="0" /> | ||
3368 | 123 | </marker> | ||
3369 | 124 | <marker | ||
3370 | 125 | inkscape:isstock="true" | ||
3371 | 126 | style="overflow:visible" | ||
3372 | 127 | id="marker5198" | ||
3373 | 128 | refX="0" | ||
3374 | 129 | refY="0" | ||
3375 | 130 | orient="auto" | ||
3376 | 131 | inkscape:stockid="Arrow2Mend"> | ||
3377 | 132 | <path | ||
3378 | 133 | transform="scale(-0.6,-0.6)" | ||
3379 | 134 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3380 | 135 | style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3381 | 136 | id="path5200" | ||
3382 | 137 | inkscape:connector-curvature="0" /> | ||
3383 | 138 | </marker> | ||
3384 | 139 | <marker | ||
3385 | 140 | inkscape:isstock="true" | ||
3386 | 141 | style="overflow:visible" | ||
3387 | 142 | id="marker4983" | ||
3388 | 143 | refX="0" | ||
3389 | 144 | refY="0" | ||
3390 | 145 | orient="auto" | ||
3391 | 146 | inkscape:stockid="Arrow2Mend"> | ||
3392 | 147 | <path | ||
3393 | 148 | transform="scale(-0.6,-0.6)" | ||
3394 | 149 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3395 | 150 | style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3396 | 151 | id="path4985" | ||
3397 | 152 | inkscape:connector-curvature="0" /> | ||
3398 | 153 | </marker> | ||
3399 | 154 | <marker | ||
3400 | 155 | inkscape:isstock="true" | ||
3401 | 156 | style="overflow:visible" | ||
3402 | 157 | id="marker4880" | ||
3403 | 158 | refX="0" | ||
3404 | 159 | refY="0" | ||
3405 | 160 | orient="auto" | ||
3406 | 161 | inkscape:stockid="Arrow2Mend"> | ||
3407 | 162 | <path | ||
3408 | 163 | transform="scale(-0.6,-0.6)" | ||
3409 | 164 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3410 | 165 | style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3411 | 166 | id="path4882" | ||
3412 | 167 | inkscape:connector-curvature="0" /> | ||
3413 | 168 | </marker> | ||
3414 | 169 | <marker | ||
3415 | 170 | inkscape:stockid="Arrow2Mend" | ||
3416 | 171 | orient="auto" | ||
3417 | 172 | refY="0" | ||
3418 | 173 | refX="0" | ||
3419 | 174 | id="marker4828" | ||
3420 | 175 | style="overflow:visible" | ||
3421 | 176 | inkscape:isstock="true" | ||
3422 | 177 | inkscape:collect="always"> | ||
3423 | 178 | <path | ||
3424 | 179 | id="path4830" | ||
3425 | 180 | style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3426 | 181 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3427 | 182 | transform="scale(-0.6,-0.6)" | ||
3428 | 183 | inkscape:connector-curvature="0" /> | ||
3429 | 184 | </marker> | ||
3430 | 185 | <marker | ||
3431 | 186 | inkscape:isstock="true" | ||
3432 | 187 | style="overflow:visible" | ||
3433 | 188 | id="marker4782" | ||
3434 | 189 | refX="0" | ||
3435 | 190 | refY="0" | ||
3436 | 191 | orient="auto" | ||
3437 | 192 | inkscape:stockid="Arrow2Mend" | ||
3438 | 193 | inkscape:collect="always"> | ||
3439 | 194 | <path | ||
3440 | 195 | transform="scale(-0.6,-0.6)" | ||
3441 | 196 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3442 | 197 | style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3443 | 198 | id="path4784" | ||
3444 | 199 | inkscape:connector-curvature="0" /> | ||
3445 | 200 | </marker> | ||
3446 | 201 | <marker | ||
3447 | 202 | inkscape:stockid="Arrow2Mend" | ||
3448 | 203 | orient="auto" | ||
3449 | 204 | refY="0" | ||
3450 | 205 | refX="0" | ||
3451 | 206 | id="Arrow2Mend" | ||
3452 | 207 | style="overflow:visible" | ||
3453 | 208 | inkscape:isstock="true" | ||
3454 | 209 | inkscape:collect="always"> | ||
3455 | 210 | <path | ||
3456 | 211 | id="path4401" | ||
3457 | 212 | style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3458 | 213 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3459 | 214 | transform="scale(-0.6,-0.6)" | ||
3460 | 215 | inkscape:connector-curvature="0" /> | ||
3461 | 216 | </marker> | ||
3462 | 217 | <marker | ||
3463 | 218 | inkscape:stockid="Arrow1Lend" | ||
3464 | 219 | orient="auto" | ||
3465 | 220 | refY="0" | ||
3466 | 221 | refX="0" | ||
3467 | 222 | id="Arrow1Lend" | ||
3468 | 223 | style="overflow:visible" | ||
3469 | 224 | inkscape:isstock="true"> | ||
3470 | 225 | <path | ||
3471 | 226 | id="path4377" | ||
3472 | 227 | d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z" | ||
3473 | 228 | style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" | ||
3474 | 229 | transform="matrix(-0.8,0,0,-0.8,-10,0)" | ||
3475 | 230 | inkscape:connector-curvature="0" /> | ||
3476 | 231 | </marker> | ||
3477 | 232 | <marker | ||
3478 | 233 | inkscape:stockid="Arrow2Mend" | ||
3479 | 234 | orient="auto" | ||
3480 | 235 | refY="0" | ||
3481 | 236 | refX="0" | ||
3482 | 237 | id="Arrow2Mend-9" | ||
3483 | 238 | style="overflow:visible" | ||
3484 | 239 | inkscape:isstock="true"> | ||
3485 | 240 | <path | ||
3486 | 241 | inkscape:connector-curvature="0" | ||
3487 | 242 | id="path4401-8" | ||
3488 | 243 | style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3489 | 244 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3490 | 245 | transform="scale(-0.6,-0.6)" /> | ||
3491 | 246 | </marker> | ||
3492 | 247 | <linearGradient | ||
3493 | 248 | id="linearGradient2795"> | ||
3494 | 249 | <stop | ||
3495 | 250 | id="stop2797" | ||
3496 | 251 | offset="0" | ||
3497 | 252 | style="stop-color:#b8b8b8;stop-opacity:0.49803922;" /> | ||
3498 | 253 | <stop | ||
3499 | 254 | id="stop2799" | ||
3500 | 255 | offset="1" | ||
3501 | 256 | style="stop-color:#7f7f7f;stop-opacity:0;" /> | ||
3502 | 257 | </linearGradient> | ||
3503 | 258 | <linearGradient | ||
3504 | 259 | id="linearGradient2787"> | ||
3505 | 260 | <stop | ||
3506 | 261 | id="stop2789" | ||
3507 | 262 | offset="0" | ||
3508 | 263 | style="stop-color:#7f7f7f;stop-opacity:0.5;" /> | ||
3509 | 264 | <stop | ||
3510 | 265 | id="stop2791" | ||
3511 | 266 | offset="1" | ||
3512 | 267 | style="stop-color:#7f7f7f;stop-opacity:0;" /> | ||
3513 | 268 | </linearGradient> | ||
3514 | 269 | <linearGradient | ||
3515 | 270 | id="linearGradient3676"> | ||
3516 | 271 | <stop | ||
3517 | 272 | id="stop3678" | ||
3518 | 273 | offset="0" | ||
3519 | 274 | style="stop-color:#b2b2b2;stop-opacity:0.5;" /> | ||
3520 | 275 | <stop | ||
3521 | 276 | id="stop3680" | ||
3522 | 277 | offset="1" | ||
3523 | 278 | style="stop-color:#b3b3b3;stop-opacity:0;" /> | ||
3524 | 279 | </linearGradient> | ||
3525 | 280 | <linearGradient | ||
3526 | 281 | id="linearGradient3236"> | ||
3527 | 282 | <stop | ||
3528 | 283 | id="stop3244" | ||
3529 | 284 | offset="0" | ||
3530 | 285 | style="stop-color:#f4f4f4;stop-opacity:1" /> | ||
3531 | 286 | <stop | ||
3532 | 287 | id="stop3240" | ||
3533 | 288 | offset="1" | ||
3534 | 289 | style="stop-color:white;stop-opacity:1" /> | ||
3535 | 290 | </linearGradient> | ||
3536 | 291 | <linearGradient | ||
3537 | 292 | id="linearGradient4671"> | ||
3538 | 293 | <stop | ||
3539 | 294 | id="stop4673" | ||
3540 | 295 | offset="0" | ||
3541 | 296 | style="stop-color:#ffd43b;stop-opacity:1;" /> | ||
3542 | 297 | <stop | ||
3543 | 298 | id="stop4675" | ||
3544 | 299 | offset="1" | ||
3545 | 300 | style="stop-color:#ffe873;stop-opacity:1" /> | ||
3546 | 301 | </linearGradient> | ||
3547 | 302 | <linearGradient | ||
3548 | 303 | id="linearGradient4689"> | ||
3549 | 304 | <stop | ||
3550 | 305 | id="stop4691" | ||
3551 | 306 | offset="0" | ||
3552 | 307 | style="stop-color:#5a9fd4;stop-opacity:1;" /> | ||
3553 | 308 | <stop | ||
3554 | 309 | id="stop4693" | ||
3555 | 310 | offset="1" | ||
3556 | 311 | style="stop-color:#306998;stop-opacity:1;" /> | ||
3557 | 312 | </linearGradient> | ||
3558 | 313 | <linearGradient | ||
3559 | 314 | gradientTransform="translate(100.2702,99.61116)" | ||
3560 | 315 | gradientUnits="userSpaceOnUse" | ||
3561 | 316 | xlink:href="#linearGradient4671" | ||
3562 | 317 | id="linearGradient2987" | ||
3563 | 318 | y2="144.75717" | ||
3564 | 319 | x2="-65.308502" | ||
3565 | 320 | y1="144.75717" | ||
3566 | 321 | x1="224.23996" /> | ||
3567 | 322 | <linearGradient | ||
3568 | 323 | gradientTransform="translate(100.2702,99.61116)" | ||
3569 | 324 | gradientUnits="userSpaceOnUse" | ||
3570 | 325 | xlink:href="#linearGradient4689" | ||
3571 | 326 | id="linearGradient2990" | ||
3572 | 327 | y2="76.313133" | ||
3573 | 328 | x2="26.670298" | ||
3574 | 329 | y1="77.475983" | ||
3575 | 330 | x1="172.94208" /> | ||
3576 | 331 | <linearGradient | ||
3577 | 332 | y2="144.75717" | ||
3578 | 333 | x2="-65.308502" | ||
3579 | 334 | y1="144.75717" | ||
3580 | 335 | x1="224.23996" | ||
3581 | 336 | gradientTransform="matrix(0.562541,0,0,0.567972,-11.5974,-7.60954)" | ||
3582 | 337 | gradientUnits="userSpaceOnUse" | ||
3583 | 338 | id="linearGradient2255" | ||
3584 | 339 | xlink:href="#linearGradient4671" | ||
3585 | 340 | inkscape:collect="always" /> | ||
3586 | 341 | <linearGradient | ||
3587 | 342 | y2="76.313133" | ||
3588 | 343 | x2="26.670298" | ||
3589 | 344 | y1="76.176224" | ||
3590 | 345 | x1="172.94208" | ||
3591 | 346 | gradientTransform="matrix(0.562541,0,0,0.567972,-11.5974,-7.60954)" | ||
3592 | 347 | gradientUnits="userSpaceOnUse" | ||
3593 | 348 | id="linearGradient2258" | ||
3594 | 349 | xlink:href="#linearGradient4689" | ||
3595 | 350 | inkscape:collect="always" /> | ||
3596 | 351 | <radialGradient | ||
3597 | 352 | gradientUnits="userSpaceOnUse" | ||
3598 | 353 | gradientTransform="matrix(1,0,0,0.177966,0,108.7434)" | ||
3599 | 354 | r="29.036913" | ||
3600 | 355 | fy="132.28575" | ||
3601 | 356 | fx="61.518883" | ||
3602 | 357 | cy="132.28575" | ||
3603 | 358 | cx="61.518883" | ||
3604 | 359 | id="radialGradient2801" | ||
3605 | 360 | xlink:href="#linearGradient2795" | ||
3606 | 361 | inkscape:collect="always" /> | ||
3607 | 362 | <linearGradient | ||
3608 | 363 | y2="137.27299" | ||
3609 | 364 | x2="112.03144" | ||
3610 | 365 | y1="192.35176" | ||
3611 | 366 | x1="150.96111" | ||
3612 | 367 | gradientTransform="matrix(0.562541,0,0,0.567972,-9.399749,-5.305317)" | ||
3613 | 368 | gradientUnits="userSpaceOnUse" | ||
3614 | 369 | id="linearGradient1475" | ||
3615 | 370 | xlink:href="#linearGradient4671" | ||
3616 | 371 | inkscape:collect="always" /> | ||
3617 | 372 | <linearGradient | ||
3618 | 373 | y2="114.39767" | ||
3619 | 374 | x2="135.66525" | ||
3620 | 375 | y1="20.603781" | ||
3621 | 376 | x1="26.648937" | ||
3622 | 377 | gradientTransform="matrix(0.562541,0,0,0.567972,-9.399749,-5.305317)" | ||
3623 | 378 | gradientUnits="userSpaceOnUse" | ||
3624 | 379 | id="linearGradient1478" | ||
3625 | 380 | xlink:href="#linearGradient4689" | ||
3626 | 381 | inkscape:collect="always" /> | ||
3627 | 382 | <radialGradient | ||
3628 | 383 | r="29.036913" | ||
3629 | 384 | fy="132.28575" | ||
3630 | 385 | fx="61.518883" | ||
3631 | 386 | cy="132.28575" | ||
3632 | 387 | cx="61.518883" | ||
3633 | 388 | gradientTransform="matrix(2.382716e-8,-0.296405,1.43676,4.683673e-7,-128.544,150.5202)" | ||
3634 | 389 | gradientUnits="userSpaceOnUse" | ||
3635 | 390 | id="radialGradient1480" | ||
3636 | 391 | xlink:href="#linearGradient2795" | ||
3637 | 392 | inkscape:collect="always" /> | ||
3638 | 393 | <linearGradient | ||
3639 | 394 | inkscape:collect="always" | ||
3640 | 395 | xlink:href="#linearGradient7897" | ||
3641 | 396 | id="linearGradient7903" | ||
3642 | 397 | x1="574.78748" | ||
3643 | 398 | y1="518.93152" | ||
3644 | 399 | x2="627.95801" | ||
3645 | 400 | y2="543.29303" | ||
3646 | 401 | gradientUnits="userSpaceOnUse" | ||
3647 | 402 | gradientTransform="translate(0,0.39292731)" /> | ||
3648 | 403 | <marker | ||
3649 | 404 | inkscape:isstock="true" | ||
3650 | 405 | style="overflow:visible" | ||
3651 | 406 | id="marker9243-9" | ||
3652 | 407 | refX="0" | ||
3653 | 408 | refY="0" | ||
3654 | 409 | orient="auto" | ||
3655 | 410 | inkscape:stockid="Arrow2Mend"> | ||
3656 | 411 | <path | ||
3657 | 412 | inkscape:connector-curvature="0" | ||
3658 | 413 | transform="scale(-0.6,-0.6)" | ||
3659 | 414 | d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" | ||
3660 | 415 | style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" | ||
3661 | 416 | id="path9245-6" /> | ||
3662 | 417 | </marker> | ||
3663 | 418 | </defs> | ||
3664 | 419 | <sodipodi:namedview | ||
3665 | 420 | id="base" | ||
3666 | 421 | pagecolor="#ffffff" | ||
3667 | 422 | bordercolor="#666666" | ||
3668 | 423 | borderopacity="1.0" | ||
3669 | 424 | inkscape:pageopacity="1" | ||
3670 | 425 | inkscape:pageshadow="2" | ||
3671 | 426 | inkscape:zoom="1.5438176" | ||
3672 | 427 | inkscape:cx="314.71375" | ||
3673 | 428 | inkscape:cy="291.80908" | ||
3674 | 429 | inkscape:document-units="px" | ||
3675 | 430 | inkscape:current-layer="layer1" | ||
3676 | 431 | showgrid="false" | ||
3677 | 432 | units="px" | ||
3678 | 433 | inkscape:showpageshadow="false" | ||
3679 | 434 | showguides="false" | ||
3680 | 435 | inkscape:guide-bbox="true" | ||
3681 | 436 | inkscape:window-width="1486" | ||
3682 | 437 | inkscape:window-height="1036" | ||
3683 | 438 | inkscape:window-x="940" | ||
3684 | 439 | inkscape:window-y="212" | ||
3685 | 440 | inkscape:window-maximized="0" | ||
3686 | 441 | fit-margin-top="20" | ||
3687 | 442 | fit-margin-left="20" | ||
3688 | 443 | fit-margin-right="20" | ||
3689 | 444 | fit-margin-bottom="20" | ||
3690 | 445 | showborder="true"> | ||
3691 | 446 | <sodipodi:guide | ||
3692 | 447 | position="470.86538,187.82982" | ||
3693 | 448 | orientation="1,0" | ||
3694 | 449 | id="guide4356" /> | ||
3695 | 450 | <sodipodi:guide | ||
3696 | 451 | position="586.76952,189.57274" | ||
3697 | 452 | orientation="0,1" | ||
3698 | 453 | id="guide4358" /> | ||
3699 | 454 | <sodipodi:guide | ||
3700 | 455 | position="381.97649,255.80368" | ||
3701 | 456 | orientation="1,0" | ||
3702 | 457 | id="guide4360" /> | ||
3703 | 458 | <sodipodi:guide | ||
3704 | 459 | position="548.4253,272.36142" | ||
3705 | 460 | orientation="1,0" | ||
3706 | 461 | id="guide4362" /> | ||
3707 | 462 | <sodipodi:guide | ||
3708 | 463 | position="508.33815,349.92133" | ||
3709 | 464 | orientation="0,1" | ||
3710 | 465 | id="guide4364" /> | ||
3711 | 466 | <sodipodi:guide | ||
3712 | 467 | position="612.91332,113.75575" | ||
3713 | 468 | orientation="0,1" | ||
3714 | 469 | id="guide4366" /> | ||
3715 | 470 | <sodipodi:guide | ||
3716 | 471 | position="548.4253,36.195832" | ||
3717 | 472 | orientation="0,1" | ||
3718 | 473 | id="guide4688" /> | ||
3719 | 474 | <sodipodi:guide | ||
3720 | 475 | position="186.76952,113.75575" | ||
3721 | 476 | orientation="1,0" | ||
3722 | 477 | id="guide4690" /> | ||
3723 | 478 | <sodipodi:guide | ||
3724 | 479 | position="68.250992,243.60325" | ||
3725 | 480 | orientation="1,0" | ||
3726 | 481 | id="guide4694" /> | ||
3727 | 482 | <sodipodi:guide | ||
3728 | 483 | position="420.02074,458.45319" | ||
3729 | 484 | orientation="0,1" | ||
3730 | 485 | id="guide5148" /> | ||
3731 | 486 | <sodipodi:guide | ||
3732 | 487 | position="170.49847,514.08991" | ||
3733 | 488 | orientation="1,0" | ||
3734 | 489 | id="guide5150" /> | ||
3735 | 490 | <sodipodi:guide | ||
3736 | 491 | position="222.20129,491.61043" | ||
3737 | 492 | orientation="1,0" | ||
3738 | 493 | id="guide5152" /> | ||
3739 | 494 | <sodipodi:guide | ||
3740 | 495 | position="160.94469,342.68385" | ||
3741 | 496 | orientation="0,1" | ||
3742 | 497 | id="guide5154" /> | ||
3743 | 498 | <sodipodi:guide | ||
3744 | 499 | position="400.91318,430.63483" | ||
3745 | 500 | orientation="0,1" | ||
3746 | 501 | id="guide5156" /> | ||
3747 | 502 | <sodipodi:guide | ||
3748 | 503 | position="303.40842,400.56852" | ||
3749 | 504 | orientation="1,0" | ||
3750 | 505 | id="guide5158" /> | ||
3751 | 506 | </sodipodi:namedview> | ||
3752 | 507 | <metadata | ||
3753 | 508 | id="metadata7"> | ||
3754 | 509 | <rdf:RDF> | ||
3755 | 510 | <cc:Work | ||
3756 | 511 | rdf:about=""> | ||
3757 | 512 | <dc:format>image/svg+xml</dc:format> | ||
3758 | 513 | <dc:type | ||
3759 | 514 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||
3760 | 515 | <dc:title></dc:title> | ||
3761 | 516 | </cc:Work> | ||
3762 | 517 | </rdf:RDF> | ||
3763 | 518 | </metadata> | ||
3764 | 519 | <g | ||
3765 | 520 | inkscape:label="Layer 1" | ||
3766 | 521 | inkscape:groupmode="layer" | ||
3767 | 522 | id="layer1" | ||
3768 | 523 | transform="translate(-139.15641,-459.62397)"> | ||
3769 | 524 | <g | ||
3770 | 525 | id="g4174" | ||
3771 | 526 | transform="translate(89.103862,103.1549)"> | ||
3772 | 527 | <rect | ||
3773 | 528 | y="534.89746" | ||
3774 | 529 | x="70.052551" | ||
3775 | 530 | height="124.65837" | ||
3776 | 531 | width="92.022942" | ||
3777 | 532 | id="rect4136" | ||
3778 | 533 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:0.13855423;fill-rule:nonzero;stroke:none;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
3779 | 534 | <rect | ||
3780 | 535 | y="538.51373" | ||
3781 | 536 | x="73.530533" | ||
3782 | 537 | height="55" | ||
3783 | 538 | width="85" | ||
3784 | 539 | id="rect4138" | ||
3785 | 540 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.50221306;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
3786 | 541 | <text | ||
3787 | 542 | sodipodi:linespacing="125%" | ||
3788 | 543 | id="text4142" | ||
3789 | 544 | y="561.84375" | ||
3790 | 545 | x="116.18498" | ||
3791 | 546 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
3792 | 547 | xml:space="preserve"><tspan | ||
3793 | 548 | y="561.84375" | ||
3794 | 549 | x="116.18498" | ||
3795 | 550 | id="tspan4144" | ||
3796 | 551 | sodipodi:role="line">Source</tspan><tspan | ||
3797 | 552 | id="tspan4146" | ||
3798 | 553 | y="580.59375" | ||
3799 | 554 | x="116.18498" | ||
3800 | 555 | sodipodi:role="line">Code</tspan></text> | ||
3801 | 556 | <rect | ||
3802 | 557 | y="600.51373" | ||
3803 | 558 | x="73.530533" | ||
3804 | 559 | height="55" | ||
3805 | 560 | width="85" | ||
3806 | 561 | id="rect4138-1" | ||
3807 | 562 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.50221306;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
3808 | 563 | <text | ||
3809 | 564 | sodipodi:linespacing="125%" | ||
3810 | 565 | id="text4148" | ||
3811 | 566 | y="623.04871" | ||
3812 | 567 | x="115.86811" | ||
3813 | 568 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
3814 | 569 | xml:space="preserve"><tspan | ||
3815 | 570 | y="623.04871" | ||
3816 | 571 | x="115.86811" | ||
3817 | 572 | id="tspan4150" | ||
3818 | 573 | sodipodi:role="line">snapcraft</tspan><tspan | ||
3819 | 574 | id="tspan4152" | ||
3820 | 575 | y="641.79871" | ||
3821 | 576 | x="115.86811" | ||
3822 | 577 | sodipodi:role="line">yaml</tspan></text> | ||
3823 | 578 | </g> | ||
3824 | 579 | <path | ||
3825 | 580 | style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)" | ||
3826 | 581 | d="m 521.13289,693.32077 c 126.36166,0 166.44881,84.53159 166.44881,160.34858" | ||
3827 | 582 | id="path4368" | ||
3828 | 583 | inkscape:connector-curvature="0" | ||
3829 | 584 | sodipodi:nodetypes="cc" /> | ||
3830 | 585 | <g | ||
3831 | 586 | id="g4174-4" | ||
3832 | 587 | transform="translate(524.45885,136.18127)"> | ||
3833 | 588 | <rect | ||
3834 | 589 | y="534.89746" | ||
3835 | 590 | x="70.052551" | ||
3836 | 591 | height="124.65837" | ||
3837 | 592 | width="92.022942" | ||
3838 | 593 | id="rect4136-5" | ||
3839 | 594 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:0.13855423;fill-rule:nonzero;stroke:none;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
3840 | 595 | <rect | ||
3841 | 596 | y="538.51373" | ||
3842 | 597 | x="73.530533" | ||
3843 | 598 | height="55" | ||
3844 | 599 | width="85" | ||
3845 | 600 | id="rect4138-8" | ||
3846 | 601 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.50221306;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
3847 | 602 | <text | ||
3848 | 603 | sodipodi:linespacing="125%" | ||
3849 | 604 | id="text4142-2" | ||
3850 | 605 | y="571.44373" | ||
3851 | 606 | x="116.18498" | ||
3852 | 607 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
3853 | 608 | xml:space="preserve"><tspan | ||
3854 | 609 | id="tspan4146-4" | ||
3855 | 610 | y="571.44373" | ||
3856 | 611 | x="116.18498" | ||
3857 | 612 | sodipodi:role="line">Binaries</tspan></text> | ||
3858 | 613 | <rect | ||
3859 | 614 | y="600.51373" | ||
3860 | 615 | x="73.530533" | ||
3861 | 616 | height="55" | ||
3862 | 617 | width="85" | ||
3863 | 618 | id="rect4138-1-6" | ||
3864 | 619 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.50221306;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
3865 | 620 | <text | ||
3866 | 621 | sodipodi:linespacing="125%" | ||
3867 | 622 | id="text4148-2" | ||
3868 | 623 | y="623.04871" | ||
3869 | 624 | x="115.86811" | ||
3870 | 625 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
3871 | 626 | xml:space="preserve"><tspan | ||
3872 | 627 | y="623.04871" | ||
3873 | 628 | x="115.86811" | ||
3874 | 629 | id="tspan4150-4" | ||
3875 | 630 | sodipodi:role="line">package</tspan><tspan | ||
3876 | 631 | id="tspan4152-7" | ||
3877 | 632 | y="641.79871" | ||
3878 | 633 | x="115.86811" | ||
3879 | 634 | sodipodi:role="line">metadata</tspan></text> | ||
3880 | 635 | </g> | ||
3881 | 636 | <g | ||
3882 | 637 | id="g4235" | ||
3883 | 638 | transform="translate(287.58171,67.973856)"> | ||
3884 | 639 | <rect | ||
3885 | 640 | y="801.38177" | ||
3886 | 641 | x="338.99783" | ||
3887 | 642 | height="122.00436" | ||
3888 | 643 | width="122.00436" | ||
3889 | 644 | id="rect4227" | ||
3890 | 645 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.50221306;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
3891 | 646 | <text | ||
3892 | 647 | sodipodi:linespacing="125%" | ||
3893 | 648 | id="text4229" | ||
3894 | 649 | y="855.71198" | ||
3895 | 650 | x="399.80914" | ||
3896 | 651 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:24px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
3897 | 652 | xml:space="preserve"><tspan | ||
3898 | 653 | y="855.71198" | ||
3899 | 654 | x="399.80914" | ||
3900 | 655 | id="tspan4231" | ||
3901 | 656 | sodipodi:role="line">Snappy</tspan><tspan | ||
3902 | 657 | id="tspan4233" | ||
3903 | 658 | y="885.71198" | ||
3904 | 659 | x="399.80914" | ||
3905 | 660 | sodipodi:role="line">Store</tspan></text> | ||
3906 | 661 | </g> | ||
3907 | 662 | <g | ||
3908 | 663 | id="g4235-9" | ||
3909 | 664 | transform="translate(42.701512,-168.19172)"> | ||
3910 | 665 | <rect | ||
3911 | 666 | y="801.38177" | ||
3912 | 667 | x="338.99783" | ||
3913 | 668 | height="122.00436" | ||
3914 | 669 | width="122.00436" | ||
3915 | 670 | id="rect4227-0" | ||
3916 | 671 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.50221306;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
3917 | 672 | <text | ||
3918 | 673 | sodipodi:linespacing="125%" | ||
3919 | 674 | id="text4229-8" | ||
3920 | 675 | y="869.47595" | ||
3921 | 676 | x="399.82413" | ||
3922 | 677 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:24px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
3923 | 678 | xml:space="preserve"><tspan | ||
3924 | 679 | id="tspan4233-2" | ||
3925 | 680 | y="869.47595" | ||
3926 | 681 | x="399.82413" | ||
3927 | 682 | sodipodi:role="line">Snapcraft</tspan></text> | ||
3928 | 683 | </g> | ||
3929 | 684 | <path | ||
3930 | 685 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker4782);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
3931 | 686 | d="m 610.02178,929.48634 c -284.09585,0 -170.80609,-75.81699 -284.09585,-75.81699" | ||
3932 | 687 | id="path4692" | ||
3933 | 688 | inkscape:connector-curvature="0" /> | ||
3934 | 689 | <path | ||
3935 | 690 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker4828);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
3936 | 691 | d="m 610.02178,929.40975 c -284.09585,0 -170.80609,75.81695 -284.09585,75.81695" | ||
3937 | 692 | id="path4692-3" | ||
3938 | 693 | inkscape:connector-curvature="0" /> | ||
3939 | 694 | <path | ||
3940 | 695 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker4880);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
3941 | 696 | d="m 610.02178,929.48634 -284.09585,0" | ||
3942 | 697 | id="path4711" | ||
3943 | 698 | inkscape:connector-curvature="0" | ||
3944 | 699 | sodipodi:nodetypes="cc" /> | ||
3945 | 700 | <g | ||
3946 | 701 | id="g4174-4-4" | ||
3947 | 702 | transform="translate(374.16692,301.34465)"> | ||
3948 | 703 | <rect | ||
3949 | 704 | y="534.89746" | ||
3950 | 705 | x="70.052551" | ||
3951 | 706 | height="187" | ||
3952 | 707 | width="92.022942" | ||
3953 | 708 | id="rect4136-5-5" | ||
3954 | 709 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:0.13855423;fill-rule:nonzero;stroke:none;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
3955 | 710 | <g | ||
3956 | 711 | id="g4347" | ||
3957 | 712 | transform="translate(0,-2.8292561e-6)"> | ||
3958 | 713 | <rect | ||
3959 | 714 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.50221306;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
3960 | 715 | id="rect4138-8-8" | ||
3961 | 716 | width="85" | ||
3962 | 717 | height="55" | ||
3963 | 718 | x="73.530533" | ||
3964 | 719 | y="538.51373" /> | ||
3965 | 720 | <text | ||
3966 | 721 | xml:space="preserve" | ||
3967 | 722 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
3968 | 723 | x="116.18498" | ||
3969 | 724 | y="571.44373" | ||
3970 | 725 | id="text4142-2-1" | ||
3971 | 726 | sodipodi:linespacing="125%"><tspan | ||
3972 | 727 | sodipodi:role="line" | ||
3973 | 728 | x="116.18498" | ||
3974 | 729 | y="571.44373" | ||
3975 | 730 | id="tspan4146-4-4">Binaries</tspan></text> | ||
3976 | 731 | </g> | ||
3977 | 732 | <g | ||
3978 | 733 | id="g4341" | ||
3979 | 734 | transform="translate(0,-0.25683877)"> | ||
3980 | 735 | <rect | ||
3981 | 736 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.50221306;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
3982 | 737 | id="rect4138-1-6-3" | ||
3983 | 738 | width="85" | ||
3984 | 739 | height="55" | ||
3985 | 740 | x="73.530533" | ||
3986 | 741 | y="600.51373" /> | ||
3987 | 742 | <text | ||
3988 | 743 | xml:space="preserve" | ||
3989 | 744 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
3990 | 745 | x="115.86811" | ||
3991 | 746 | y="623.04871" | ||
3992 | 747 | id="text4148-2-9" | ||
3993 | 748 | sodipodi:linespacing="125%"><tspan | ||
3994 | 749 | sodipodi:role="line" | ||
3995 | 750 | id="tspan4150-4-6" | ||
3996 | 751 | x="115.86811" | ||
3997 | 752 | y="623.04871">package</tspan><tspan | ||
3998 | 753 | sodipodi:role="line" | ||
3999 | 754 | x="115.86811" | ||
4000 | 755 | y="641.79871" | ||
4001 | 756 | id="tspan4152-7-0">metadata</tspan></text> | ||
4002 | 757 | </g> | ||
4003 | 758 | <g | ||
4004 | 759 | id="g4335"> | ||
4005 | 760 | <rect | ||
4006 | 761 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.50221306;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
4007 | 762 | id="rect4138-1-6-3-3" | ||
4008 | 763 | width="85" | ||
4009 | 764 | height="55" | ||
4010 | 765 | x="74.001198" | ||
4011 | 766 | y="662.00006" /> | ||
4012 | 767 | <text | ||
4013 | 768 | xml:space="preserve" | ||
4014 | 769 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
4015 | 770 | x="116.33875" | ||
4016 | 771 | y="684.53503" | ||
4017 | 772 | id="text4148-2-9-4" | ||
4018 | 773 | sodipodi:linespacing="125%"><tspan | ||
4019 | 774 | sodipodi:role="line" | ||
4020 | 775 | x="116.33875" | ||
4021 | 776 | y="684.53503" | ||
4022 | 777 | id="tspan4152-7-0-7">digital</tspan><tspan | ||
4023 | 778 | sodipodi:role="line" | ||
4024 | 779 | x="116.33875" | ||
4025 | 780 | y="703.28503" | ||
4026 | 781 | id="tspan4354">signature</tspan></text> | ||
4027 | 782 | </g> | ||
4028 | 783 | </g> | ||
4029 | 784 | <g | ||
4030 | 785 | id="g5113" | ||
4031 | 786 | transform="translate(-6.743845,-3.9339095)"> | ||
4032 | 787 | <rect | ||
4033 | 788 | y="502.17679" | ||
4034 | 789 | x="256.82809" | ||
4035 | 790 | height="69.124413" | ||
4036 | 791 | width="119.70325" | ||
4037 | 792 | id="rect4981" | ||
4038 | 793 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:0.13855423;fill-rule:nonzero;stroke:#7e7e7e;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:3, 1;stroke-dashoffset:1;stroke-opacity:1;marker:none;marker-end:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
4039 | 794 | <text | ||
4040 | 795 | sodipodi:linespacing="125%" | ||
4041 | 796 | id="text5107" | ||
4042 | 797 | y="533.09399" | ||
4043 | 798 | x="316.85104" | ||
4044 | 799 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
4045 | 800 | xml:space="preserve"><tspan | ||
4046 | 801 | y="533.09399" | ||
4047 | 802 | x="316.85104" | ||
4048 | 803 | id="tspan5109" | ||
4049 | 804 | sodipodi:role="line">Snapcraft</tspan><tspan | ||
4050 | 805 | id="tspan5111" | ||
4051 | 806 | y="551.84399" | ||
4052 | 807 | x="316.85104" | ||
4053 | 808 | sodipodi:role="line">Cloud Parts</tspan></text> | ||
4054 | 809 | </g> | ||
4055 | 810 | <g | ||
4056 | 811 | id="g5113-2" | ||
4057 | 812 | transform="translate(146.95962,-3.6528961)"> | ||
4058 | 813 | <rect | ||
4059 | 814 | y="502.17679" | ||
4060 | 815 | x="256.82809" | ||
4061 | 816 | height="69.124413" | ||
4062 | 817 | width="250" | ||
4063 | 818 | id="rect4981-9" | ||
4064 | 819 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:0.13855423;fill-rule:nonzero;stroke:#7e7e7e;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:3, 1;stroke-dashoffset:1;stroke-opacity:1;marker:none;marker-end:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
4065 | 820 | <text | ||
4066 | 821 | sodipodi:linespacing="125%" | ||
4067 | 822 | id="text5107-3" | ||
4068 | 823 | y="494.31686" | ||
4069 | 824 | x="383.92838" | ||
4070 | 825 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
4071 | 826 | xml:space="preserve"><tspan | ||
4072 | 827 | id="tspan5111-3" | ||
4073 | 828 | y="494.31686" | ||
4074 | 829 | x="383.92838" | ||
4075 | 830 | sodipodi:role="line">Common Repositories</tspan></text> | ||
4076 | 831 | </g> | ||
4077 | 832 | <path | ||
4078 | 833 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker5350);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
4079 | 834 | d="m 442.56482,584.78891 0,27.81836" | ||
4080 | 835 | id="path5160" | ||
4081 | 836 | inkscape:connector-curvature="0" /> | ||
4082 | 837 | <path | ||
4083 | 838 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker5271);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
4084 | 839 | d="m 266.10088,700.55825 95.25682,0" | ||
4085 | 840 | id="path5162" | ||
4086 | 841 | inkscape:connector-curvature="0" /> | ||
4087 | 842 | <path | ||
4088 | 843 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker5198);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
4089 | 844 | d="m 309.65488,584.78891 c 0,44.67797 6.46286,84.29806 51.70282,84.57906" | ||
4090 | 845 | id="path5164" | ||
4091 | 846 | inkscape:connector-curvature="0" /> | ||
4092 | 847 | <path | ||
4093 | 848 | inkscape:connector-curvature="0" | ||
4094 | 849 | id="path5463-4" | ||
4095 | 850 | style="fill:#1b1817;fill-opacity:1;fill-rule:evenodd;stroke:none" | ||
4096 | 851 | d="m 439.90668,510.58478 c -11.2425,0 -20.36,9.11625 -20.36,20.3625 0,8.995 5.83375,16.6275 13.925,19.32125 1.01875,0.18625 1.39,-0.4425 1.39,-0.9825 0,-0.48375 -0.0175,-1.76375 -0.0275,-3.4625 -5.66375,1.23 -6.85875,-2.73 -6.85875,-2.73 -0.92625,-2.3525 -2.26125,-2.97875 -2.26125,-2.97875 -1.84875,-1.2625 0.14,-1.2375 0.14,-1.2375 2.04375,0.14375 3.11875,2.09875 3.11875,2.09875 1.81625,3.11125 4.76625,2.2125 5.92625,1.69125 0.185,-1.315 0.71125,-2.2125 1.2925,-2.72125 -4.52125,-0.515 -9.275,-2.26125 -9.275,-10.06375 0,-2.22375 0.79375,-4.04 2.09625,-5.46375 -0.21,-0.515 -0.90875,-2.585 0.2,-5.38875 0,0 1.70875,-0.5475 5.59875,2.08625 1.62375,-0.45125 3.36625,-0.67625 5.0975,-0.685 1.73,0.009 3.47125,0.23375 5.0975,0.685 3.8875,-2.63375 5.59375,-2.08625 5.59375,-2.08625 1.11125,2.80375 0.4125,4.87375 0.20375,5.38875 1.305,1.42375 2.0925,3.24 2.0925,5.46375 0,7.8225 -4.76125,9.54375 -9.29625,10.0475 0.73,0.62875 1.38125,1.87125 1.38125,3.77125 0,2.72125 -0.025,4.9175 -0.025,5.585 0,0.545 0.3675,1.17875 1.4,0.98 8.085,-2.69875 13.91375,-10.325 13.91375,-19.31875 0,-11.24625 -9.1175,-20.3625 -20.36375,-20.3625" /> | ||
4097 | 852 | <g | ||
4098 | 853 | id="g5903" | ||
4099 | 854 | transform="matrix(0.33153681,0,0,0.33153681,478.85446,509.13681)"> | ||
4100 | 855 | <path | ||
4101 | 856 | id="path1948" | ||
4102 | 857 | d="m 60.510156,6.3979729 c -4.583653,0.021298 -8.960939,0.4122177 -12.8125,1.09375 C 36.35144,9.4962267 34.291407,13.691825 34.291406,21.429223 l 0,10.21875 26.8125,0 0,3.40625 -26.8125,0 -10.0625,0 c -7.792459,0 -14.6157592,4.683717 -16.7500002,13.59375 -2.46182,10.212966 -2.5710151,16.586023 0,27.25 1.9059283,7.937852 6.4575432,13.593748 14.2500002,13.59375 l 9.21875,0 0,-12.25 c 0,-8.849902 7.657144,-16.656248 16.75,-16.65625 l 26.78125,0 c 7.454951,0 13.406253,-6.138164 13.40625,-13.625 l 0,-25.53125 c 0,-7.266339 -6.12998,-12.7247775 -13.40625,-13.9375001 -4.605987,-0.7667253 -9.385097,-1.1150483 -13.96875,-1.09375 z m -14.5,8.2187501 c 2.769547,0 5.03125,2.298646 5.03125,5.125 -2e-6,2.816336 -2.261703,5.09375 -5.03125,5.09375 -2.779476,-1e-6 -5.03125,-2.277415 -5.03125,-5.09375 -1e-6,-2.826353 2.251774,-5.125 5.03125,-5.125 z" | ||
4103 | 858 | style="fill:url(#linearGradient1478);fill-opacity:1" | ||
4104 | 859 | inkscape:connector-curvature="0" /> | ||
4105 | 860 | <path | ||
4106 | 861 | id="path1950" | ||
4107 | 862 | d="m 91.228906,35.054223 0,11.90625 c 0,9.230755 -7.825895,16.999999 -16.75,17 l -26.78125,0 c -7.335833,0 -13.406249,6.278483 -13.40625,13.625 l 0,25.531247 c 0,7.26634 6.318588,11.54032 13.40625,13.625 8.487331,2.49561 16.626237,2.94663 26.78125,0 6.750155,-1.95439 13.406253,-5.88761 13.40625,-13.625 l 0,-10.218747 -26.78125,0 0,-3.40625 26.78125,0 13.406254,0 c 7.79246,0 10.69625,-5.435408 13.40624,-13.59375 2.79933,-8.398886 2.68022,-16.475776 0,-27.25 -1.92578,-7.757441 -5.60387,-13.59375 -13.40624,-13.59375 l -10.062504,0 z m -15.0625,64.65625 c 2.779478,3e-6 5.03125,2.277417 5.03125,5.093747 -2e-6,2.82635 -2.251775,5.125 -5.03125,5.125 -2.76955,0 -5.03125,-2.29865 -5.03125,-5.125 2e-6,-2.81633 2.261697,-5.093747 5.03125,-5.093747 z" | ||
4108 | 863 | style="fill:url(#linearGradient1475);fill-opacity:1" | ||
4109 | 864 | inkscape:connector-curvature="0" /> | ||
4110 | 865 | <ellipse | ||
4111 | 866 | transform="matrix(0.73406,0,0,0.809524,16.24958,27.00935)" | ||
4112 | 867 | id="path1894" | ||
4113 | 868 | style="opacity:0.44382025;fill:url(#radialGradient1480);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||
4114 | 869 | cx="61.518883" | ||
4115 | 870 | cy="132.28575" | ||
4116 | 871 | rx="48.948284" | ||
4117 | 872 | ry="8.6066771" /> | ||
4118 | 873 | </g> | ||
4119 | 874 | <image | ||
4120 | 875 | y="510.65253" | ||
4121 | 876 | x="539.22363" | ||
4122 | 877 | id="image7798" | ||
4123 | 878 | xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAR0AAAEdCAYAAAAxarN7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ | ||
4124 | 879 | bWFnZVJlYWR5ccllPAAAIjZJREFUeNrsnV1sFeedxl+7EBwMGJavtgngrltpVyrEm/ZiIRecVEVK | ||
4125 | 880 | uIkjdSWqRPXpXuCVWlKWCLJSaepSclHYUDa0F+Ymh4iokbZSzA1klSoxKwW0F8k6JHdbWqekTR1A | ||
4126 | 881 | 2ID5SGi684zfgbE5Z+adc2bej3mfRzoyCeZ8zJn5zfP/eP9vm6CoBJ19ZNkDwY/FWf5Nz2sXTvLI | ||
4127 | 882 | UY3UxkPgHUTWBD+6Yw+oEvuVSgEvOxo8Jmb9eSL6cwCpd/nNEDqU22DpCn70ysdiCZI4ZGzUmHxE | ||
4128 | 883 | UBrBnwMgTfIbJXQoOwFTiYGmu0QfMXJEI/InQPQBv3lCh9IHmQdikKmUDDBZQDQSPRieETpUvpBZ | ||
4129 | 884 | EwNMn8iY1CWEKEKHUgHNRgmYKGSismlMAmg4ANAxHg5Ch6oPmsckaOhm8tdw9GBimtAhaAgaAojQ | ||
4130 | 885 | oQoGDRLBVfkgaMxpIgYfhmCETulA0xUDDXM09mkseNTwYDme0CmDq9kuYUO5E37V6H4IHddg0y9B | ||
4131 | 886 | U+HRcNr9DArmfggdB0IoOJtuHpHSCLmfgwy9CB3bYLNdPpgYLrdqcD+ED6FD2FCED6FD2FCED0Xo | ||
4132 | 887 | EDZUUToo4cOEM6GTG3BQjRoUTBBTjRUlnA8SPoROK7DZKE8kNvRRqhqTrucIDwWhkwU2a6SzqfJo | ||
4133 | 888 | UE1qBKE4x2wQOirA+YEEDvM2VB5ivofQaQgbLFmoMZSiCgq5tnNpBaETwaZLOpvtPB2ogoV1XVXf | ||
4134 | 889 | XY/X0JGJYribbl4PlCZNSPB463q8hA7dDUXXQ+joBA5zNxRdD6GjDTioTB3kuU5ZJq8qXF5AR4ZT | ||
4135 | 890 | cDd9PL8pSzUqXU/p+3pKDx0ZTo0I9t1QboRb28vezdzuQTg1SuBQjgjnaS04b1+k03EznEKcXOV5 | ||
4136 | 891 | TDkcblXKmOcpHXTkuimUI1mdosoQblXKlucpVXgl8zejBA5VonBrVI5WIXQsBE6/YP6GKqeQ5/k5 | ||
4137 | 892 | wyu7gPNjMd1hTFGlho+Yrm45nedxHjoy01/l+Uh5IucTzM5Chw1/FMHjJnichI4EzohgwpjyV85W | ||
4138 | 893 | tpyDDoFDUW6DxynoEDhUM7rnb78qOtZtEPOCn3NWrg4eq8Tc4Cd0/cxb4c9Pzr4vbv7ufXHt9HHx | ||
4139 | 894 | 2dRlgofQIXCobGrvXCS6+gbEgk1bbgNGVVOnjoup0yfE1d+8QvD4Ch0Ch8oKmyVP7mr5uT4d/4M4 | ||
4140 | 895 | //w2ceO9UwSPT9AhcChVdazdIJY/fSizs1FxPucPbHMh7HICPFZDh8ChVLXkiZ25uJsk1zO+p198 | ||
4141 | 896 | 8rv3CZ6yQofAoVS1fMcLYuGmbxf+On+5Oik+eqaP4Gk1BCZwKJe1dOteLcCBPregS3zhZ8NhNcxy | ||
4142 | 897 | Yf3hiJy4QOgoqkbgUGla8M0touvxAa2vCfCsfPZImLB2ADzD8gZO6KS4HKyl4tIGKlFzVqwSSwf2 | ||
4143 | 898 | GnltJKqX7zjkwmHqlY7HKvBYBR25WrzKS4pKDasC4MB1mFLnhs1htcwR8NQInfrAwTycQV5OVJpw | ||
4144 | 899 | seOiN60iq2U5q8+muctWQCe2AR5FpQrNfzbo3nUPueJ2oKotEwiNQ0dm2Ed4KVEqQi7HBpdjGwAV | ||
4145 | 900 | hQmEG72GjkxwYYg6R4xSSpq/4VGr3o9NAFTUsOlSummng21iWBqn1EOatQ9Z954cCrGEsKCUbgw6 | ||
4146 | 901 | ciO8Ki8jKtMFvs4+6Nxr4XtKUa+84fsDHZk4PshLiMp0snYuMlombyQHOpTryVhiWTt0YkscKKoU | ||
4147 | 902 | F3e7hSBUVE0agNI7nZpg4piibJH2/I5W6Mg8Dpc4UJQ96haaUx1zNAKHeRwHhD4YzBBGyDAvFs4g | ||
4148 | 903 | tEkLI27IecMQ5g1/dnUyHHzlwCgI34X8znDPaxeOlQY6sT2qKEsEiOAxNwAMKkLxYeXNKqmKgyFY | ||
4149 | 904 | t8bP3R6Afiv476xjQPHvqcKE/E5vAJ4PyuJ0BgX7cYwKvSRh237wMFHiBdDwmP3a2I0BILr+3luh | ||
4150 | 905 | U0oaCXrrYzuhE3d4DmuxNAYPF/1ChU8OlG3XI7zs9TsZbLvSuX6zU30kV17/lTh/4KmGf48hWrZ9 | ||
4151 | 906 | no+f3+bSzhFpwl7p/+EsdGRYhS1Qu4kBPaBZ+M0t4VKBvIeT6xJGgn7wT19u+PdFz0JuRn/of9Ba | ||
4152 | 907 | F9aEMOq00DCr6PBqkMApOD5esSqEDBYeugqauND8h8/U6CLGflQ2Qefm2ffKBBwtYVZhJXNZrdpO | ||
4153 | 908 | LBQj5GhW/uiIWH3kHbFs4LlSACdS0qJOVMJwoduiyeHDZTy9KrK9xS3oCFarChHmAq+qvS2+uO+Y | ||
4154 | 909 | iyuclZS2qNOWCx2hYIlyOXdFKUU1DRYCHUlJVqtyFHIZgM2KAjaTs02AKUKsRsKFjhK8aV0c2l3m | ||
4155 | 910 | rwFhViF9dblDR87qGCQmcg6n1j1UetjEtXDTlsS/x3a/JoUQr8QuJ1K1iKFfRTidQcG1VbnL9EWm | ||
4156 | 911 | W4v6BhK3eUFj4eSrQ8bCqqSyfsmUu9v5XM4uZ6PgUodChKa59s4u0fH3X/fi87bf0yH++snNxK7l | ||
4157 | 912 | 62+/YcQBXji0M3xtT/T5H3x5/sQLv732P7Y6HQKnQF16eV94l6XbuaPxPd/RWs0qWSOgcvSSZ1I5 | ||
4158 | 913 | N6cjBwL9C9FQnP766c3AAcxzcVJd025n7v1fEVP/PZx4TKZOvirm/d3XC3c8ngIH6sAjcDv/ZQ10 | ||
4159 | 914 | JAXxbTCXk+WiCu7iK/7tsGgLLi7VldgINxZs2mLlBL0idM+qr4QLRD/98LeJ4IlgUASQUSn76Jk+ | ||
4160 | 915 | n0KqevrHIMyqBeBp2WrnFV6hCbCbGMlw61i7QayqvROWh7FbZZa9sS8d3e/VscIWvkkl9Dvh537x | ||
4161 | 916 | p12P5RpuIVn9x+89zPEcMsyywunEXE4HvxM1Ld26Vyx/6t/D8CEKI9rmdijfSXEB+FRCx/HB4lWE | ||
4162 | 917 | UXA1ScKShCvHjwTu5Fw4/6eZY4S8GV5rfE9/GNqlvaZH6g3czkjgdlpal9WWA3R+LNiXoxxOYZX0 | ||
4163 | 918 | vJ61df/+wwx3VDgldCX7JDgYhDlJ4y9mK1qbhtX2WBDbKCzFc+PYY21X2ogNzzXS89qFltZltQQd | ||
4164 | 919 | 6XLGBHM56bmJ4IQHcJJyMZgtg4tKVVh7VdalEHmCp953EYWzAA0Bk1mVADwnm775tvji2wmcdGG9 | ||
4165 | 920 | 1P2/fDM1+YskKH5XVSVvw68ruETAu5WdIQAaJOTxIHCaUkuRTdM5HeZy1IT8zdJ//pHy7yNXc+V4 | ||
4166 | 921 | TSmP4FvD4O2Q6W9Wis6Nj4tPP/y/xKoWVZi6W8nttOJ06HJStHzHC6Lr8YFsd4HADWE2jqp8axiM | ||
4167 | 922 | H6fPP/tSGGKqVLYoe9xOUzkd5nJSSN65KCyDL9z07aafI8s0ukV9W8OZOr4K0L08PCQmgwfDJb2O | ||
4168 | 923 | p5kJg806nSqB0xg4yDm0ApzQJT19SPl3Lw8ftmLUg0nXg2mC6HtCOJun88nSP0W3U6zT+b1gM2BD | ||
4169 | 924 | 4DQqiWfVn/d8R1w7fULpd+evfzQMN6hpocp15TeviBtnTmVq7MN3iLxaZ3A856/fHG6V02q1jG6n | ||
4170 | 925 | RejINVY1HutigQPBvaAbVvWEt3GnBFuEdoRbcu+tekra+yuPMn2Z3U4AnZ8UDZ03gx8VHutZ4dCO | ||
4171 | 926 | F1oOqerp0tF9YXu/inxsGNTpnAieupqQbke5mpEJOnLY+iiPsx7gRMqSVM7zvSBBG4Um2BDvs6nk | ||
4172 | 927 | 86pDuqw8dgsleJxSNYDOkaKg86KYTiJTUkhcZi2LNxMaqHYqI4mKHSKauaCQ/2h2y996irYuxp7o | ||
4173 | 928 | 9/R8tRShX9pmgJ5qNIDOP+QOHVkmn+DxvSN0D6/IUGVqRVmSyiob0iFfdO3UCaXtfPNUtL0xEt95 | ||
4174 | 929 | 5r90CivPLx7ezQtgprBB37t5Qwc7PHAyYOwujqUNupQlqYykNsrHs5ddRKBBVceGUQ3RYkzsSuoa | ||
4175 | 930 | gDwe6NVItQA6380bOv8ruK1M4kVdtLIkleMNg1OnjoegUXVKpiDeFbxnlKldGVD2IefsxKWcUFaC | ||
4176 | 931 | DhPIM3XfL94wdmfOklRGmHXl9Vec2vYWQMcyEExHtD0ZnbWlwQMpJZRVO5KrPJ7TQuLYZCiQpVMZ | ||
4177 | 932 | rsi1fbZxAeN9n6t+LQxhbF5XBihiqiF1W0rbiBM6GYQkaNGVqjRFSVgfhJzJueqDYVhpK3wwzwih | ||
4178 | 933 | LBWqV2622Rp0gid5THCdVWj7V1qyzCDrTGWXFTkfhDHITdko5M5ame9TMqX2drTn8SQ+CDbalgQn | ||
4179 | 934 | bH2W8RdlEMLE8Z/2h60DNroeNGVSaiEWoaMghDO2jQX1JcSaLVTgEHLZ5nqQ50PinhLdsvDUHHQY | ||
4180 | 935 | Wk2HVbYlC8NtUb7/DW+/E4RccD22JZrRkMkwK92otLfyj30Q8ie2hFW4wHChsRt2Wkg0Y3mITbOE | ||
4181 | 936 | cL5QhE7TQrWqyIWcWYGDC4xdsDOF5jwkmXXuZ56krMP1S6rEKlZ7Qmi10ffQKm39ki7hgkIeg92v | ||
4182 | 937 | jcMtABmLMW1xO5w42NiwtNPl1BfuVjasiuY4BXXwYPW3DeDJOlyf0Lmjit8ux3wlgsDJLlvAA5fs | ||
4183 | 938 | +S4VFTmZQg06Mh7zdnEnSp+m1/0QOGUAj/cl9EoWp+Oty0EsvsiwNSZwygEeFCF8dzuEjoIQi5ss | ||
4184 | 939 | kUdVKgInH/CYrmp57nb6skDH2ySySZdD4OQv0308nrud7nql87ugI1uYvSyVo2Jl0uVcHNrNsnjO | ||
4185 | 940 | CruX9/Qb7Vz23O1UVJyOvwlkgycHljaw8a8YAeQAuilhGqLHfTtK0Kn4eGTQfWyqYoW8A5c2FCsA | ||
4186 | 941 | 3VRiGe4ZkxA9VS+h00Cmmrlg+7mlib7w1VR+x+Nmwd7Z/TozoCP/stu3o4JEn6nRFZde3sc8jiaF | ||
4187 | 942 | XcvPbzPy2nDRcNN0O3c7HS/zOdgGxVRYdXn4MGmgUdhE0FSYtdDfEKuSBB2GVhrFsMpcmGWimoXy | ||
4188 | 943 | uacJZTqduDB0yUQCGXdbhlXmwiyEtUZc9frNPh5yQmfG3cfA7BPcZS8d3c+r36AQ1ppIKnf6OWa2 | ||
4189 | 944 | O55Mvg0dX5PIJvI5l4eHnNuPqowyAX4ULHwPsdoZWukNreByJgPoUOaF3h0Tbsf3EMtr6HSs01/C | ||
4190 | 945 | vPr6K1xb5bnbuXedl6Xz7nrQ8W69VaeBOw5dDt0Onc4dVXw7CrrHkaJixVyOheB5Xe+aNyyL8HCr | ||
4191 | 946 | mrrQ6fbtKOhuErvyOhd02nke6P9eOvwLsW5HUnNcgQ6WKsxZicdqMXflnfkk18+8FeZImul5iZrz | ||
4192 | 947 | dGwzAwuPbljKPsF9YsdQnUth7l37kHfd6Nhhpue1CyfnyP9YY+ObnN53aktwV3ioYZVpSezPANDU | ||
4193 | 948 | 6eOZkrW6wHPt1Ale3RZr6vQJrdDpsGCnEVNqt9HlYJjWqtrb4ov7joUwUC1rI0ezbOA50f3rs+GG | ||
4194 | 949 | 9qoT23TM073CWTlWS/csI+R1PJwoWLEOOkiu3feLN8SKpw+13D8DWK0+8o7ypvZYj1PUPF2EVlzy | ||
4195 | 950 | 4IDbCUIsrU7eU7djDXQAh/t/+aaY17M23+d9clcIsrS7SrRZWxELARlauRNi6VQ8N+mj0zEqhEJF | ||
4196 | 951 | buELkN0XAC2tTAk3UsRCwOvvvcUr2gHdOKP3e/Ld6VRMAkdH9Qgx9Bd+NpwKniIWAuo+manmhCqW | ||
4197 | 952 | zkbBOf45nW7jTmfp1r1agDMbPGkL7vKcLoc8EZc90O3UD69W+3Z4zUJn/vpHRdfj+odnATwrn30p | ||
4198 | 953 | +cR771RuSeUbZ9ib45Juak74+zjC1Eh4BaexfMchYx8apfVFfVsTf2cyp8atm6xaOaVPzvL7KlIY | ||
4199 | 954 | oWPE6Sx5YpfRTe2i95AUZqFvI49K1i2Du0tSTThTzV3j9/qXTO7VDh2Urk2EVfXCrLTZyNdOH3fu | ||
4200 | 955 | JKZa16e8UWgJr7TJpon4afuWt9q3YXIrW6oVd6pvEoCHFSzRLvcu1yabdjqE25mfMLO21UoGu5Bd | ||
4201 | 956 | hY7Osrl3Faxwlbm24V2mdl5IEgZlX2vgaFDqRhUr7y5pym5dP3OqKRggCf3ZVDZ3++m4d/OVFs/R | ||
4202 | 957 | +Wo2zhBJ6wqF1W4WOqyEuCkUEa5ygW5R0ptInrvCPiuZ5rxaCZGy3vUoygdphc49PXaOaPRwdCRF | ||
4203 | 958 | +QEdaw9CQr8Om/soitDRqs9Y9qYoQoeiKEKntLrX41m2FOU8dGxdh8SlChRVUujYmJRNW2fjY5s6 | ||
4204 | 959 | RRUprc2BNs6WSWvga6WcHjYevryfZ5ljwoybVsPqv0xNKjWHNrtnm8Ma0wodHFwsgjQ91iKutEWd | ||
4205 | 960 | XALhn7DHva5JCNir7aNn+ryCTjt23NP5ilct2loXAEwaX9HqVDc2HbopnU2sPs5b0l69mhwesubD | ||
4206 | 961 | AzhJ84txx2tFNjk6KsNFkTJDO1/onPPv+Op+QUzcL3o3TVVdOpqcb5m/4dHW75p0O86JIXXJoKNy | ||
4207 | 962 | set5D/tCADYEzvpHcxnDQeg4Flpp/r48XGYzFkFnVLfbuTD0Q2OfGmXytDAvbZSp8l2T0CF0EuTb | ||
4208 | 963 | Mpue1y58EEFnQveLY1M73XtHQ0gej+/pT8zl5FEyvX0S9xA6ToVWmqHjY2Oq0WUQ5w9sy21/KVVd | ||
4209 | 964 | HNqd2heR5xbHXEbhlnQOmvNwhvZEHDqjJt4B3AZ6FHSB5+Pnt6VOhEMuJ29Q+LihmpN34M5FWpPI | ||
4210 | 965 | Hs7QHo1DZ8LUu4jAU2SohTvKh997OBU4RW0CSLfjisvR+z35Os7WOHQi8Iz/tD9MLudtOQGzc9UH | ||
4211 | 966 | le4qAE4RvTVJO05Q9qhT8/d003OnM2rDO0JyGYDIo48H7eV/2vVYCLOkpHGkJU/sFJ0bNhfyuWDZ | ||
4212 | 967 | sckgZbfmr9+s9fU8DK8mrHE6s13P+QNPibFv9YTOJ0u+By4JwAJsELKpVgYWfHNLrsnjuif0Brod | ||
4213 | 968 | u4HzqPYOcl9zOuGCz57XLrx79pFlVr07wAfOBw/kWtA/EeVG8Of24ASJ9hnC3kH4Apv5EgGcFU8f | ||
4214 | 969 | KvzzLAxeB5+FYmgVOXEPNXEbOlJjwaPbxncKAMG15N3TsKhvq1g28JyWz4AQC7Dkrp/2CTc13aHV | ||
4215 | 970 | DT+hMyOnE0HHqxNNF3AidQWQo2wMrTZrD618dDpBRDU5GzqjPh2AaMtg3Se3zhXMlJqWPLlT+2t6 | ||
4216 | 971 | 2Ik8cvuGPzve8km6JxnibprXmi4qH6FxM4+FvVlkYvmPBRqrB50R347CFQP7VS/YtIVXulUuZ5f2 | ||
4217 | 972 | 10ybVukTdEZ9OwrR+FSdwl0VFTPKDpdjolvc0yTy3eGVTPJ4F2IljSstSksH9jK346nLQR4xaY6T | ||
4218 | 973 | b07HS7djwuoyt2NeRSzstTWkt0ATmKPTCDoj/jmdE6l7XxWhRQF0uDTCnOA2jZxvp7zM58wwM947 | ||
4219 | 974 | HVMnAtyOqRPf+7DqiZ3aK1ahqz513NfQaoTQmSVTO1RggSlXoOsV3KWJXI6pUN56pyPjLu+Sybj7 | ||
4220 | 975 | mOoQxTgNJpU1Hm8N6+zqCVXSq37mc1LDq7uskC+6YmgTQIRZRQwOo+qHVaYGqtm0yaRmjcWTyIRO | ||
4221 | 976 | /KQI7kKmZtYizFrEdVmFCottTYVVJkN4C3QXTwidmC4bPDGw+JR7ZBUjhK8rnz1i0EX/ytcE8l2h | ||
4222 | 977 | VV3oYLaO8DCvE92NTE7ox4XB/E4Rx/UlI9Uq06G7S07HW7czPTjMnNvBhfGFnw0TPDlq+Y4XjA7G | ||
4223 | 978 | R4HCx72tpMakiSF00u5KJt0Ohn2xfycfIXG8cNO3jb4HbF9Nl0PoJAqx92XDST9cKLhDU81Lx9xr | ||
4224 | 979 | upwcoSMt0ZivR8p0bofgaR04K54234bgucuBhrM4Ha/djuncDsHjPnBQsfLc5YxG40mzQGfY5yMG | ||
4225 | 980 | t2NiISjB07yQw7EBOHDJl47up8tpoIbQCSh1zOcjBrdzcWi3Fe8F4LnvF2+wqpUggNl0DicSXLLH | ||
4226 | 981 | fTnNQ4duZ3rshS1T+1HVuu+Xb7KBcPYJHIAYbQamq1SR4I497j6OVLdUTugo6vzz26x5L1EfD8ed | ||
4227 | 982 | TgsABohN9uHUO19UtrH21eUQOgqCTbapCoEFoshb+J7nQf7m/gA4JjuNZwvzcjxPHkeqNQ0dmX32 | ||
4228 | 983 | HjyXXt6vfY8sqr4wDwduz5b8TSQkj88f2MYvKCW0UnE6dDuRbT7wlPHenfgJbkuSW7e7sS2cunN+ | ||
4229 | 984 | MKxS5YUqdCZ8P5LYrubSy3aEWXgfPp3g2CpmVe3t0N3o3v5XRZOvDoVFByo9tILaVJ7l7CPLXgx+ | ||
4230 | 985 | VHk8hVj5oyPh/BtTQpj3x+9/wxvYADQ2Opv49/HRM310OXdCqy/l4XQYYs2y0SabBrOEVa7OXwZs | ||
4231 | 986 | kLf54r5jVgNnOo/zFIFzRwdVfkkJOrJRcIzHdLppcHxPv5HXztJaD+B8/tmXpsOSJ3Zav90N+m3Q | ||
4232 | 987 | CoAmSNthE78BIOymspmTNtVnC0KsHwc/Bnlcp6V7jQ/uqueqDyrfVQGb2eVklHSxI4FNA8IBx87g | ||
4233 | 988 | MX/9ZivzNY2EPM7Fw7t5IcSAE5iTx/OGzhq6nZlaunWv6Hpcz06dF4Z+KC4PH1b6XcxbxvjTJEUA | ||
4234 | 989 | wr7aOlv24Wg6AhfjImjix278p/28AGaqT3XpVFuWZw3A8yqenMf3jtCkV3QLfpbkMS7qVbV3Ml3M | ||
4235 | 990 | yFEBPtfPnBK38OccG9zQNYzHvODRsW5DuJzDZTFxXFdKCeRIczI+eY3QuTuuDy+qAi+mLMlj7JGe | ||
4236 | 991 | 1T0gDJu7afUMeAJEt8bPiU/Ovh9cYNP9SUnr0O7p+ar4XOf068LJAH6uA6YenAmchlxQVlvWZw/c | ||
4237 | 992 | zu+DH908zjPdBaotRVxkWaw8ksWrj7zDL6QAIacG4DBxXFeLG83OycPpQINZyVZ24c6HEzJv8GTt | ||
4238 | 993 | PF7y5E5+GSmhUeRS4ODmrFwl2qUrhFtt5BAJnGSXkwU4zUIn6lBezONdLHiyzGVBb4st4x1scSbI | ||
4239 | 994 | UyFZDlioAAOOFfBBuR5VNXyPBE6qDmb9B23NvArL58WHWsgfnKt+Tfn38Zou9LYULeSdsKNHHm0B | ||
4240 | 995 | UW8TB3I11Ejgch7OfI00a6l4vJMdT6ur0rPM8cFd2XfgADZ/2vVYeOzz6kMCbAicRDVlPNqafTWu | ||
4241 | 996 | xyrO8WTtA6nXCOiL4AgBaM6xccPltOJ0mqacb44HAMmai8iUPH5ip7fAQVcwQlACx4iajnaahk5A | ||
4242 | 997 | uQ8YZqWDB44Fa6ZUlSV5DDe1qG/Au+MKMP95z3e4DMGc0Ax4RDt06HbUhZXIHyvkaBAqYEqhusvZ | ||
4243 | 998 | 5eQyglaBAwfJ+TVG1dJ139bqqzO3oy6UY5HnaQQKJEJVQwUfGwGbLV+jnQCJdnRKozdndjiKJDSW | ||
4244 | 999 | f9wMnvfaqRNMHqe7nC+18gR5QIcLQbNYyyAkWvnsS3dVm7Imj00PE7MdOIAyloQs2LQlsxtE5XFy | ||
4245 | 1000 | +LBVq/EtUiWAzkmj0JHg+XnwYzu/D3UhARwNF8cF9cfvPZypERAzZ3ySqgsE1JcO7M2lUTIMd4/u | ||
4246 | 1001 | J3zuqOmK1YzvKMcYb4LfibqQu/kwAA3uqll3hLRtJ4SihbEeKsBBvxJW2OfVmY0wDDOTEBJzd9Xb | ||
4247 | 1002 | 13nLasvr3bBLWY90Dw8zLeRbEFalqejZRlwOoT6kS5fTgbAGY4xYKDgs82xRp0pnNmYaFT1MDXkh | ||
4248 | 1003 | OB6Pt3XOLX2SG3TkSlM6nSKB41kjIJr/0sJOHUPUCB4xKPvy7IKOBA8ahkaIh/zlWyMgwpm0fcYQ | ||
4249 | 1004 | aupeWR+Bx6Mcz4RoYiW5NujkbcOoWM5iYK9XjYDXTh9PnNCHkjiOiQnhe1i+w5u82vas83K0Q0fu | ||
4250 | 1005 | Y3yQmKBaEUrViWHV04eMQhg9Uq7uK5ZBI60sd9DpdMIYUDCpnKuwlOIP/Q+G67hs2VO9KKGNICmX | ||
4251 | 1006 | E3UY2+A+y+5yCkkVFPGk0o4xzMpZuBABH+x/hd4VkzuNFqkrKc14tvQpIalfYrczKKOW3NVW5Lvm | ||
4252 | 1007 | ljXFK9qsrkyjStE02agfxrY1ZyXdA6vl9VUmwqtIVcFO5UKF1dZwP2Pf6glXsmed32Ojkhrw5m+w | ||
4253 | 1008 | y1kgt1PCSla1yCcvFDoyzKoSDcULlR6sEcJdNw4g1/I/SXtrhRf5evsWuXaUa1TswVYXdBoNrxhm | ||
4254 | 1009 | 2SE0syEEw8WRtNWKSdBM7zD6VuoaqzX/+Vvr3v+lo/syzUGyOawKHr15l8jvCpE12jV8IG5bYyhc | ||
4255 | 1010 | CUMWeWHEt/rFzpy6QATXFb6Xs++Hs2tUt4a5bcuDMMbGXqUSdSj3FQ0cbdDBBwncDsAzTATYA6Gr | ||
4256 | 1011 | s8OEtRvCn1E5GgOv5sSWXdQbgBWHye1QL/bfgEv0361ux2vrxd1ejqbNwqpVppwOwHMsAA+aBllK | ||
4257 | 1012 | t1RRaMNB594JTYA/0QZp3TQNHqP8jinKGqG6XNXqDHW+WKyaxTI6Rdmhap4ryG10OtHaLIZYVGbd | ||
4258 | 1013 | GufA9JyF8rj2ubftJj6pXERW43dOZYKOpbs03EjpLbJUo8F1+K8mXrjd1CcOPvB3BfM7VEZdt/AC | ||
4259 | 1014 | v+neCFOkNyqmXrzd8IevCOZ3KMddhYNOp6KjH8dK6MgPXuGlRKlqyrKdPbHUpNX+I82q6urHsdXp | ||
4260 | 1015 | RInlKi8nSkVoMrQpxJpya3vjWhFDuZyDjgQPDgSnDVJKuvK6HZvfYZ6RQxvxjcg8qnG123JEZCa9 | ||
4261 | 1016 | xkuKShMudBvcTtpIVYuEgo01C67bLTs42wUrWpSCLg7tNvr6gJ4jLiesVJlMHFsNnVhimeChEoXc | ||
4262 | 1017 | DkZKmBAWuKpsAkjguOF04uBhKZ1KDm9e3h8Oqtet8we2WduoWAc479r2xtptPFoED5UlzMLuEbqE | ||
4263 | 1018 | iYzX3KhYVW0EjrXQkeB5l+Ch0oQemY+e6Svc8SCkAnAcyeNUTaypUlWb7Ufv7CPLHhDTWxVz6iCV | ||
4264 | 1019 | qEV9W8Wygedyf144KQy//8SN5Q5VG3pxnIYOwUNlEaYLYhO8PDbjg7u5PDzk0vxj64HjDHQIHiqr | ||
4265 | 1020 | MHq1q28g3CKmWdhMBg+Hljg4ARynoEPwUM0Iw9znr98cOJ8N08Poe9bW/T303WBg/PX33nIlUewk | ||
4266 | 1021 | cJyDDsFDUW4Dx0noEDwUFWpCWF6lKhV0JHjWiOktbXp5/lEeAqdiax9OaaEjwdMlHQ/BQxE4jqjd | ||
4267 | 1022 | 5aMf61zmJn6UD8KaxG6XgeO805nlel4UHAZGlVe4sVZtW7zpNXQkePoFZ/JQ5VPNlgFchE598GyU | ||
4268 | 1023 | dwVWtqgyyLmSuHfQkeBhZYtyXc4njBupvYzfltwmtcJQi3JUpUgYe+V0ZrmeHwgOfafc0UFTO28S | ||
4269 | 1024 | OvmC5wEZbnXznKYsDqec7DBmeFU/3IJN7WW4RVmqEZyfPgDHG6czy/X0y3CL1S3KBg0GsPmJTx+4 | ||
4270 | 1025 | zcdvWVa34HoqPOcpQxoLHn1lTRYTOo3hgyTzIF0PpVkHpcOZ9PHDt/n+7dP1UJrdDZLFJ30+CG08 | ||
4271 | 1026 | D+h6KC3yLndD6KiBp0ta3yqPBpWTRoLHdh9zN4RONvhslCFXN48G1aQmJGyO8FAQOgy5qMJDKTHd | ||
4272 | 1027 | WTzJQ0HoNBty4STazqNBpWhYupsPeCgInTzgs0bCp8qjQc3SiJhOFJ/koSB0ioDPRgmfCo+G9xoT | ||
4273 | 1028 | LIETOoQPpQk2g0wSEzqED6UjjKoRNoSOLfBhzqfcsGHOhtCxGj4AD6pdLLW7rZqYLn2zsY/QcQI+ | ||
4274 | 1029 | KLX3SfhwVrM7GovBhn02hI6zANoo3U8f3Y+1Qo9NzZdBWoQO3Q9lztVgrd0wG/oIHR8AtCYGoG4e | ||
4275 | 1030 | Ea2giVwNczWEjrcAekACqI8OiKAhdChTDgiPCo9I0xqVoBkmaAgdSh1AXRI8FQkhhmHJbmZEPoZZ | ||
4276 | 1031 | eSJ0qPxcUAShXs9DsQgycDQjdDOEDqXPCfXOglAZ3dBEBBf5c5TVJkKHsg9EEYCiP7vSHzQiXUzk | ||
4277 | 1032 | ZMYIGEKHchdIG+UfK7N+dmtySJFjEfLnRPwn8zCEDuW3U4prsVDLH0UOZcb/o1Oh4vp/AQYAVCEX | ||
4278 | 1033 | Gt0EzcEAAAAASUVORK5CYII= | ||
4279 | 1034 | " | ||
4280 | 1035 | preserveAspectRatio="none" | ||
4281 | 1036 | height="38.276688" | ||
4282 | 1037 | width="38.276688" /> | ||
4283 | 1038 | <text | ||
4284 | 1039 | xml:space="preserve" | ||
4285 | 1040 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:24px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
4286 | 1041 | x="591.80872" | ||
4287 | 1042 | y="531.6861" | ||
4288 | 1043 | id="text8871" | ||
4289 | 1044 | sodipodi:linespacing="125%"><tspan | ||
4290 | 1045 | sodipodi:role="line" | ||
4291 | 1046 | id="tspan8873" | ||
4292 | 1047 | x="591.80872" | ||
4293 | 1048 | y="531.6861">……</tspan></text> | ||
4294 | 1049 | <rect | ||
4295 | 1050 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#linearGradient7903);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-end:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
4296 | 1051 | id="rect7801" | ||
4297 | 1052 | width="97.053047" | ||
4298 | 1053 | height="81.728882" | ||
4299 | 1054 | x="574.12695" | ||
4300 | 1055 | y="494.32266" /> | ||
4301 | 1056 | <g | ||
4302 | 1057 | id="g8879" | ||
4303 | 1058 | transform="translate(-0.27784156,15.003444)"> | ||
4304 | 1059 | <path | ||
4305 | 1060 | sodipodi:nodetypes="czsssc" | ||
4306 | 1061 | inkscape:connector-curvature="0" | ||
4307 | 1062 | id="path8875" | ||
4308 | 1063 | d="m 249.76963,912.25001 c -9.61837,-7.34936 -19.09612,0.32907 -18.33733,7.25045 0.75879,6.92138 6.8411,11.37657 13.72067,11.42422 l 50.15039,0.3473 c 4.03101,0.0279 12.08612,-8.47417 10.0023,-16.84414 -2.08381,-8.36998 -14.30884,-16.77469 -26.39494,-5.93887" | ||
4309 | 1064 | style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||
4310 | 1065 | <path | ||
4311 | 1066 | sodipodi:nodetypes="cc" | ||
4312 | 1067 | inkscape:connector-curvature="0" | ||
4313 | 1068 | id="path8877" | ||
4314 | 1069 | d="m 243.62484,909.3225 c 0.55568,-15.55913 26.95063,-23.61653 39.4535,-3.3341" | ||
4315 | 1070 | style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||
4316 | 1071 | </g> | ||
4317 | 1072 | <g | ||
4318 | 1073 | id="g4347-5" | ||
4319 | 1074 | transform="translate(143.43127,303.02174)"> | ||
4320 | 1075 | <g | ||
4321 | 1076 | id="g9018" | ||
4322 | 1077 | transform="translate(51.678531,-9.1687717)"> | ||
4323 | 1078 | <rect | ||
4324 | 1079 | ry="5" | ||
4325 | 1080 | rx="5" | ||
4326 | 1081 | y="533.23474" | ||
4327 | 1082 | x="70.613205" | ||
4328 | 1083 | height="54.72216" | ||
4329 | 1084 | width="28.32032" | ||
4330 | 1085 | id="rect4138-8-8-9-9" | ||
4331 | 1086 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.50221306;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
4332 | 1087 | <rect | ||
4333 | 1088 | y="538.65265" | ||
4334 | 1089 | x="73.53054" | ||
4335 | 1090 | height="43.886337" | ||
4336 | 1091 | width="22.485647" | ||
4337 | 1092 | id="rect4138-8-8-9" | ||
4338 | 1093 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.50221306;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
4339 | 1094 | </g> | ||
4340 | 1095 | </g> | ||
4341 | 1096 | <g | ||
4342 | 1097 | id="g9789" | ||
4343 | 1098 | transform="translate(7.125194,1.2954898)"> | ||
4344 | 1099 | <rect | ||
4345 | 1100 | y="1003.0229" | ||
4346 | 1101 | x="253.4267" | ||
4347 | 1102 | height="7.7959223" | ||
4348 | 1103 | width="18.228931" | ||
4349 | 1104 | id="rect9022" | ||
4350 | 1105 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-end:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> | ||
4351 | 1106 | <g | ||
4352 | 1107 | id="g9756"> | ||
4353 | 1108 | <rect | ||
4354 | 1109 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-end:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
4355 | 1110 | id="rect9022-7" | ||
4356 | 1111 | width="5.1752357" | ||
4357 | 1112 | height="5.5057745" | ||
4358 | 1113 | x="284.96884" | ||
4359 | 1114 | y="1004.7013" /> | ||
4360 | 1115 | <path | ||
4361 | 1116 | style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
4362 | 1117 | d="m 263.23546,1007.3397 23.17593,0.229" | ||
4363 | 1118 | id="path9237" | ||
4364 | 1119 | inkscape:connector-curvature="0" | ||
4365 | 1120 | sodipodi:nodetypes="cc" /> | ||
4366 | 1121 | <path | ||
4367 | 1122 | style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
4368 | 1123 | d="m 287.55645,1005.5162 0,-7.78642" | ||
4369 | 1124 | id="path9239" | ||
4370 | 1125 | inkscape:connector-curvature="0" /> | ||
4371 | 1126 | <circle | ||
4372 | 1127 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-end:url(#marker9243);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
4373 | 1128 | id="path9241" | ||
4374 | 1129 | cx="287.55646" | ||
4375 | 1130 | cy="996.92834" | ||
4376 | 1131 | r="1.2954898" /> | ||
4377 | 1132 | <path | ||
4378 | 1133 | style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||
4379 | 1134 | d="m 288.13774,996.68547 c 0,0 14.33135,-5.91067 14.41232,-0.72871 0.081,5.18194 -14.41232,0.72871 -14.41232,0.72871 z" | ||
4380 | 1135 | id="path9595" | ||
4381 | 1136 | inkscape:connector-curvature="0" | ||
4382 | 1137 | sodipodi:nodetypes="czc" /> | ||
4383 | 1138 | <path | ||
4384 | 1139 | style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||
4385 | 1140 | d="m 286.87091,996.68547 c 0,0 -14.33134,-5.91067 -14.41231,-0.72871 -0.081,5.18194 14.41231,0.72871 14.41231,0.72871 z" | ||
4386 | 1141 | id="path9595-5" | ||
4387 | 1142 | inkscape:connector-curvature="0" | ||
4388 | 1143 | sodipodi:nodetypes="czc" /> | ||
4389 | 1144 | </g> | ||
4390 | 1145 | <g | ||
4391 | 1146 | transform="translate(0,0.51071554)" | ||
4392 | 1147 | id="g9764"> | ||
4393 | 1148 | <rect | ||
4394 | 1149 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-end:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
4395 | 1150 | id="rect9022-7-5" | ||
4396 | 1151 | width="5.1752357" | ||
4397 | 1152 | height="5.5057745" | ||
4398 | 1153 | x="-240.11348" | ||
4399 | 1154 | y="1004.1906" | ||
4400 | 1155 | transform="scale(-1,1)" /> | ||
4401 | 1156 | <path | ||
4402 | 1157 | style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
4403 | 1158 | d="m 261.84687,1006.8289 -23.17593,0.229" | ||
4404 | 1159 | id="path9237-4" | ||
4405 | 1160 | inkscape:connector-curvature="0" | ||
4406 | 1161 | sodipodi:nodetypes="cc" /> | ||
4407 | 1162 | <path | ||
4408 | 1163 | style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||
4409 | 1164 | d="m 237.52588,1005.0054 0,-7.78637" | ||
4410 | 1165 | id="path9239-4" | ||
4411 | 1166 | inkscape:connector-curvature="0" /> | ||
4412 | 1167 | <circle | ||
4413 | 1168 | style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-end:url(#marker9243-9);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" | ||
4414 | 1169 | id="path9241-9" | ||
4415 | 1170 | cx="-237.52586" | ||
4416 | 1171 | cy="996.41754" | ||
4417 | 1172 | r="1.2954898" | ||
4418 | 1173 | transform="scale(-1,1)" /> | ||
4419 | 1174 | <path | ||
4420 | 1175 | style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||
4421 | 1176 | d="m 236.94459,996.17473 c 0,0 -14.33135,-5.9107 -14.41232,-0.7287 -0.081,5.18187 14.41232,0.7287 14.41232,0.7287 z" | ||
4422 | 1177 | id="path9595-59" | ||
4423 | 1178 | inkscape:connector-curvature="0" | ||
4424 | 1179 | sodipodi:nodetypes="czc" /> | ||
4425 | 1180 | <path | ||
4426 | 1181 | style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||
4427 | 1182 | d="m 238.21142,996.17473 c 0,0 14.33134,-5.9107 14.41231,-0.7287 0.081,5.18187 -14.41231,0.7287 -14.41231,0.7287 z" | ||
4428 | 1183 | id="path9595-5-0" | ||
4429 | 1184 | inkscape:connector-curvature="0" | ||
4430 | 1185 | sodipodi:nodetypes="czc" /> | ||
4431 | 1186 | </g> | ||
4432 | 1187 | <path | ||
4433 | 1188 | inkscape:connector-curvature="0" | ||
4434 | 1189 | id="path9772" | ||
4435 | 1190 | d="m 257.53179,1008.2639 -3.72453,8.5826" | ||
4436 | 1191 | style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||
4437 | 1192 | <path | ||
4438 | 1193 | inkscape:connector-curvature="0" | ||
4439 | 1194 | id="path9772-8" | ||
4440 | 1195 | d="m 267.80726,1008.2639 3.72453,8.5826" | ||
4441 | 1196 | style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||
4442 | 1197 | </g> | ||
4443 | 1198 | </g> | ||
4444 | 1199 | </svg> | ||
4445 | 0 | 1200 | ||
4446 | === added file 'md_importer/tests/data/snapcraft-test/docs/snapcraft-advanced-features.md' | |||
4447 | --- md_importer/tests/data/snapcraft-test/docs/snapcraft-advanced-features.md 1970-01-01 00:00:00 +0000 | |||
4448 | +++ md_importer/tests/data/snapcraft-test/docs/snapcraft-advanced-features.md 2016-01-19 00:21:38 +0000 | |||
4449 | @@ -0,0 +1,233 @@ | |||
4450 | 1 | # Snapcraft: Advanced features | ||
4451 | 2 | |||
4452 | 3 | Once you have built [your first snap](your-first-snap.md), you will probably | ||
4453 | 4 | want to learn more about snapcraft's more advanced features. Having a look at | ||
4454 | 5 | our selection of examples is a good idea, as we want it to be a good showcase | ||
4455 | 6 | of what is possible and generally relevant. | ||
4456 | 7 | |||
4457 | 8 | ## Examples | ||
4458 | 9 | |||
4459 | 10 | Our showcase can be found in the actual source of `snapcraft` itself. Check | ||
4460 | 11 | it out by simply running: | ||
4461 | 12 | |||
4462 | 13 | git clone https://github.com/ubuntu-core/snapcraft | ||
4463 | 14 | cd snapcraft/examples | ||
4464 | 15 | |||
4465 | 16 | Inspecting the source locally will make easier to build the examples and | ||
4466 | 17 | play around with them. (You can | ||
4467 | 18 | [view them online](https://github.com/ubuntu-core/snapcraft/tree/master/examples) | ||
4468 | 19 | as well.) | ||
4469 | 20 | |||
4470 | 21 | ### Playing around with the examples | ||
4471 | 22 | |||
4472 | 23 | If you just cloned the `snapcraft` source and inspect the examples, you | ||
4473 | 24 | can start off your explorations by reading the accompanying `snapcraft.yaml` | ||
4474 | 25 | file and running: | ||
4475 | 26 | |||
4476 | 27 | ../../bin/snapcraft snap | ||
4477 | 28 | |||
4478 | 29 | This will inform you of all the steps taken during the creation of the snap. | ||
4479 | 30 | |||
4480 | 31 | ## Defining your parts | ||
4481 | 32 | |||
4482 | 33 | Once you have noted down all the general information about your snap | ||
4483 | 34 | (like description, summary information and everything else), naming | ||
4484 | 35 | the individual parts will define the stucture of your `snapcraft.yaml` file. | ||
4485 | 36 | Think of parts as individual components of your snap: Where do you pull them | ||
4486 | 37 | from? How are they built? | ||
4487 | 38 | |||
4488 | 39 | ### Prerequisites during the build | ||
4489 | 40 | |||
4490 | 41 | The example named `downloader-with-wiki-parts` shows how very easy you can | ||
4491 | 42 | make sure that the relevant build dependencies are installed: | ||
4492 | 43 | |||
4493 | 44 | build-packages: [libssl-dev] | ||
4494 | 45 | |||
4495 | 46 | The above will install the `libssl-dev` package from the Ubuntu archive before | ||
4496 | 47 | an attempted build. If you need a specific version of libssl-dev or a custom | ||
4497 | 48 | build, you will need to specify a separate part. | ||
4498 | 49 | |||
4499 | 50 | Also note that the above will not define which libraries are shipped with the | ||
4500 | 51 | app. It merely makes sure you have all the relevant build tools installed. | ||
4501 | 52 | |||
4502 | 53 | |||
4503 | 54 | ### Pulling and building | ||
4504 | 55 | |||
4505 | 56 | If you just intend to pull and build the source, take a look at the `gopaste` | ||
4506 | 57 | example. In its `snapcraft.yaml` file you can find just this one `parts` | ||
4507 | 58 | paragraph: | ||
4508 | 59 | |||
4509 | 60 | parts: | ||
4510 | 61 | gopaste: | ||
4511 | 62 | plugin: go | ||
4512 | 63 | source: git://github.com/wisnij/gopaste/gopasted | ||
4513 | 64 | |||
4514 | 65 | It starts off with the name of the specific part (`gopaste` here), the origin | ||
4515 | 66 | of the part (it's a `git` URL) and how to build it (plugin: `go`). | ||
4516 | 67 | Other possible scenarios would be Bazaar or Mercurial branches, or local | ||
4517 | 68 | directories. | ||
4518 | 69 | |||
4519 | 70 | ### Mixing and matching plugins | ||
4520 | 71 | |||
4521 | 72 | An interesting example is `py2-project` because it defines two parts | ||
4522 | 73 | `spongeshaker` using the `python2` plugin, and `make-project` using the | ||
4523 | 74 | `make` plugin. | ||
4524 | 75 | |||
4525 | 76 | parts: | ||
4526 | 77 | spongeshaker: | ||
4527 | 78 | plugin: python2 | ||
4528 | 79 | source: git://github.com/markokr/spongeshaker.git | ||
4529 | 80 | make-project: | ||
4530 | 81 | plugin: make | ||
4531 | 82 | source: . | ||
4532 | 83 | |||
4533 | 84 | The example above mixes and matches parts of different origin. Locally it | ||
4534 | 85 | provides a binary we intend to ship (the `sha3sum.py` script) and a | ||
4535 | 86 | `Makefile` (to install our script in the right place). | ||
4536 | 87 | |||
4537 | 88 | `spongeshaker` is a python library we will need to pull from git, build as | ||
4538 | 89 | a python project and bundle along with our script. | ||
4539 | 90 | |||
4540 | 91 | A possible use-case for the above would be if you just intend to ship a small | ||
4541 | 92 | binary which you maintain, but require a library you need to build from git | ||
4542 | 93 | as opposed to simply including it from the Ubuntu archives. | ||
4543 | 94 | |||
4544 | 95 | What's happening during the `snapcraft` run is: | ||
4545 | 96 | |||
4546 | 97 | 1. Get `spongeshaker` from `git`. | ||
4547 | 98 | 1. Build it as a python project (which will include installing the python | ||
4548 | 99 | library in the right place). | ||
4549 | 100 | 1. Running `make` (from the local `Makefile`) and thus installing our | ||
4550 | 101 | `sha3sum.py` script in the right place. | ||
4551 | 102 | |||
4552 | 103 | ### Putting your parts in order | ||
4553 | 104 | |||
4554 | 105 | If your app is comprised of multiple parts, it might be necessary to build | ||
4555 | 106 | and stage parts in a particular order. This can be done by using the `after` | ||
4556 | 107 | keyword: | ||
4557 | 108 | |||
4558 | 109 | parts: | ||
4559 | 110 | pipelinetest: | ||
4560 | 111 | plugin: make | ||
4561 | 112 | source: lp:~mterry/+junk/pipelinetest | ||
4562 | 113 | after: | ||
4563 | 114 | - libpipeline | ||
4564 | 115 | libpipeline: | ||
4565 | 116 | plugin: autotools | ||
4566 | 117 | source: lp:~mterry/libpipeline/printf | ||
4567 | 118 | |||
4568 | 119 | |||
4569 | 120 | In the case of the `libpipeline` example above, the part named `pipelinetest` | ||
4570 | 121 | will be built after `libpipeline`. Especially if you need specific | ||
4571 | 122 | functionality during a build or as part of checks during the `stage` phase, | ||
4572 | 123 | this will be handy. | ||
4573 | 124 | |||
4574 | 125 | ### Re-using parts | ||
4575 | 126 | |||
4576 | 127 | With snapcraft we want to make it easy to learn from other app vendors and | ||
4577 | 128 | re-use parts which have worked well for them. | ||
4578 | 129 | |||
4579 | 130 | In the `downloader-with-wiki-parts` example, you can see that the `main` | ||
4580 | 131 | part is built: | ||
4581 | 132 | |||
4582 | 133 | after: | ||
4583 | 134 | - curl | ||
4584 | 135 | |||
4585 | 136 | As we never define the `curl` part in the above example, `snapcraft` will | ||
4586 | 137 | check the Ubuntu Wiki, which is where we currently host examples of | ||
4587 | 138 | successful snapcraft parts. The build order in this case would be `curl`, | ||
4588 | 139 | then `main`. | ||
4589 | 140 | |||
4590 | 141 | |||
4591 | 142 | ## Finishing steps | ||
4592 | 143 | |||
4593 | 144 | ### Individual files | ||
4594 | 145 | |||
4595 | 146 | If you are planning to provide binaries and services to the users of your | ||
4596 | 147 | apps, you need to specify them in your definition first. It's just a matter | ||
4597 | 148 | of enumerating them. | ||
4598 | 149 | |||
4599 | 150 | The `godd` example has one binary: | ||
4600 | 151 | |||
4601 | 152 | binaries: | ||
4602 | 153 | godd: | ||
4603 | 154 | exec: ./bin/godd | ||
4604 | 155 | |||
4605 | 156 | The above will take care of making the script executable, adding it to the | ||
4606 | 157 | user's path and install it in the right place. | ||
4607 | 158 | |||
4608 | 159 | For a simple service we can take a look at `gopaste`: | ||
4609 | 160 | |||
4610 | 161 | services: | ||
4611 | 162 | gopaste: | ||
4612 | 163 | description: "gopaste" | ||
4613 | 164 | start: bin/gopasted | ||
4614 | 165 | |||
4615 | 166 | You define a name for the service, describe it (so log messages are more | ||
4616 | 167 | descriptive), declare how to run the service and that's it. For more | ||
4617 | 168 | thoughts on services and their security, visit the | ||
4618 | 169 | [snappy policy](https://developer.ubuntu.com/en/snappy/guides/security-policy/). | ||
4619 | 170 | |||
4620 | 171 | |||
4621 | 172 | ### Limiting the number of installed files | ||
4622 | 173 | |||
4623 | 174 | To check the list of files included in your snap, you can use `unsquashfs -l` | ||
4624 | 175 | on the resulting `.snap` file. If you find that certain files should not be | ||
4625 | 176 | shipped to the user (download size being just one factor), you can | ||
4626 | 177 | explicitly tell `snapcraft` which files to snap: | ||
4627 | 178 | |||
4628 | 179 | snap: | ||
4629 | 180 | - usr/lib/x86_64-linux-gnu/libgudev-1.0.so* | ||
4630 | 181 | - usr/lib/x86_64-linux-gnu/libobject-2.0.so* | ||
4631 | 182 | - usr/lib/x86_64-linux-gnu/libglib-2.0.so* | ||
4632 | 183 | - bin/godd* | ||
4633 | 184 | |||
4634 | 185 | Here `godd` further defines the list of files to be placed in the app | ||
4635 | 186 | during the `snap` phase. As you can see above, globs (using asterisks as | ||
4636 | 187 | wildcard characters) are a good way of handling complexities within the | ||
4637 | 188 | directory structure. | ||
4638 | 189 | |||
4639 | 190 | In the `webcam-webui` example you can see the following part called `cam`: | ||
4640 | 191 | |||
4641 | 192 | cam: | ||
4642 | 193 | plugin: go | ||
4643 | 194 | go-packages: | ||
4644 | 195 | - github.com/mikix/golang-static-http | ||
4645 | 196 | stage-packages: | ||
4646 | 197 | - fswebcam | ||
4647 | 198 | filesets: | ||
4648 | 199 | fswebcam: | ||
4649 | 200 | - usr/bin/fswebcam | ||
4650 | 201 | - lib | ||
4651 | 202 | - usr/lib | ||
4652 | 203 | go-server: | ||
4653 | 204 | - bin/golang-* | ||
4654 | 205 | stage: | ||
4655 | 206 | - $fswebcam | ||
4656 | 207 | - $go-server | ||
4657 | 208 | snap: | ||
4658 | 209 | - $fswebcam | ||
4659 | 210 | - $go-server | ||
4660 | 211 | - -usr/share/doc | ||
4661 | 212 | |||
4662 | 213 | In the `stage` definition you can see how named filesets are re-used | ||
4663 | 214 | (`$fswebcam` and `$go-server`). | ||
4664 | 215 | |||
4665 | 216 | Another feature used in the `snap` definition is an exclude (`-usr/share/doc` | ||
4666 | 217 | in this case), meaning that files in these directories will not be installed. | ||
4667 | 218 | |||
4668 | 219 | |||
4669 | 220 | ### node.js | ||
4670 | 221 | |||
4671 | 222 | Snapping node.js apps has never been this easy. Take a look at the `shout` | ||
4672 | 223 | example and see how short and sweet it is. To bundle node packages, you simply | ||
4673 | 224 | do something like: | ||
4674 | 225 | |||
4675 | 226 | parts: | ||
4676 | 227 | shout: | ||
4677 | 228 | plugin: nodejs | ||
4678 | 229 | node-packages: | ||
4679 | 230 | - shout | ||
4680 | 231 | |||
4681 | 232 | `node-packages` simply lists which packages (including their dependencies) to | ||
4682 | 233 | add to the snap. | ||
4683 | 0 | 234 | ||
4684 | === added file 'md_importer/tests/data/snapcraft-test/docs/snapcraft-parts.md' | |||
4685 | --- md_importer/tests/data/snapcraft-test/docs/snapcraft-parts.md 1970-01-01 00:00:00 +0000 | |||
4686 | +++ md_importer/tests/data/snapcraft-test/docs/snapcraft-parts.md 2016-01-19 00:21:38 +0000 | |||
4687 | @@ -0,0 +1,130 @@ | |||
4688 | 1 | # Snapcraft parts | ||
4689 | 2 | |||
4690 | 3 | Parts are the main building block to create snaps using Snapcraft. Parts have | ||
4691 | 4 | their own private space and lifecycle. Each part uses a `plugin`, which tells | ||
4692 | 5 | the part how to behave and what to do with the information inside it. | ||
4693 | 6 | |||
4694 | 7 | As seen in the [article about snapcraft.yaml syntax](snapcraft-syntax.md) | ||
4695 | 8 | parts have general keywords that apply to all of them. In one case, you may | ||
4696 | 9 | want to enhance your part's functionality using `stage-packages` which end up | ||
4697 | 10 | bringing Ubuntu deb-based packages into your part, `filesets` to declare | ||
4698 | 11 | inclusion and exclusion sets, `organize` to make the artifact output for your | ||
4699 | 12 | part neater, `stage` and `snap` to make certain only the right set of files is | ||
4700 | 13 | seen at each step (making use of `filesets` or not). An example integrating | ||
4701 | 14 | these concepts for a part called `example-part` using a hypothetical plugin | ||
4702 | 15 | called `sample` would look like: | ||
4703 | 16 | |||
4704 | 17 | parts: | ||
4705 | 18 | example-part: | ||
4706 | 19 | type: sample | ||
4707 | 20 | stage-packages: | ||
4708 | 21 | - gpg | ||
4709 | 22 | - wget | ||
4710 | 23 | organize: | ||
4711 | 24 | opt/bin: bin | ||
4712 | 25 | filesets: | ||
4713 | 26 | binaries: | ||
4714 | 27 | - bin/* | ||
4715 | 28 | - usr/bin/* | ||
4716 | 29 | headers: | ||
4717 | 30 | - *.h | ||
4718 | 31 | - -include | ||
4719 | 32 | stage: | ||
4720 | 33 | - $binaries | ||
4721 | 34 | - test/bin/test_app | ||
4722 | 35 | - $headers | ||
4723 | 36 | snap: | ||
4724 | 37 | - $binaries | ||
4725 | 38 | |||
4726 | 39 | In this example, imagine that the `sample` plugin actually builds something in | ||
4727 | 40 | its private *build* location using its private *source* directory as a base, | ||
4728 | 41 | and that it *installs* the usual set of files from its private install | ||
4729 | 42 | directory. | ||
4730 | 43 | |||
4731 | 44 | This `sample` plugin makes use of `stage-packages`, these packages will be | ||
4732 | 45 | fetched from the Ubuntu deb archive using the *series* (release, i.e.; trusty, | ||
4733 | 46 | vivid, wily, ...) that is being used on the host. In this case, the part will | ||
4734 | 47 | be enhanced by the *gpg* and *wget* deb packages and its necessary | ||
4735 | 48 | dependencies to work isolated inside the part. | ||
4736 | 49 | |||
4737 | 50 | When reaching the *stage* phase, the components in the private part's | ||
4738 | 51 | *install* directory will be exposed there, but since we used the organize | ||
4739 | 52 | keyword the contents in the install directory will be exposed to other parts | ||
4740 | 53 | in a cleaner form if desired or required; it is important to notice that in | ||
4741 | 54 | the event of using `filesets` they will follow the organized files and not | ||
4742 | 55 | the internal layout. | ||
4743 | 56 | |||
4744 | 57 | The concept of `filesets` basically allows the creation of sets named after | ||
4745 | 58 | the keywords defined within, in this case *binaries* and *headers*, these are | ||
4746 | 59 | not necessarily needed but allow for variable expansion in the common | ||
4747 | 60 | targets: `stage` and `snap`. An inclusion is defined by just listing the | ||
4748 | 61 | target file, it can be globbed with `*` and a file can be explicitly | ||
4749 | 62 | excluded by prepending a `-` (when using `*` at the beginning of a path it | ||
4750 | 63 | needs to be quoted). | ||
4751 | 64 | |||
4752 | 65 | The `stage` keyword will replace *$binaries* with all the *binaries* defined | ||
4753 | 66 | in `filesets`, but it also adds *test/bin/test_app* to the `stage` file set; | ||
4754 | 67 | *$headers* will basically *include* all the header files except those that | ||
4755 | 68 | live in *include* as it has a `-` in front of it. These are the files that | ||
4756 | 69 | will make it to the *stage* directory. | ||
4757 | 70 | |||
4758 | 71 | The behavior for `snap` is identical to `stage` with the exception of applying | ||
4759 | 72 | this in the snap directory, which is the final layout for the snap, this is | ||
4760 | 73 | where everything should look clean and crisp for a good quality snap. | ||
4761 | 74 | |||
4762 | 75 | |||
4763 | 76 | ## Snapcraft for Python with PIP | ||
4764 | 77 | |||
4765 | 78 | Snapcraft includes support for Python 2.x and Python 3.x parts; here's how a | ||
4766 | 79 | `snapcraft.yaml` parts section will look like: | ||
4767 | 80 | |||
4768 | 81 | parts: | ||
4769 | 82 | spongeshaker: | ||
4770 | 83 | plugin: python3 | ||
4771 | 84 | source: git://github.com/markokr/spongeshaker.git | ||
4772 | 85 | |||
4773 | 86 | A Python part will typically make sure required Python packages are installed | ||
4774 | 87 | on the build host and embed the following pieces in your snap: | ||
4775 | 88 | |||
4776 | 89 | * latest Python runtime from the latest Ubuntu packages of your current | ||
4777 | 90 | Ubuntu release | ||
4778 | 91 | * latest PIP for this Python version as downloaded from PyPy | ||
4779 | 92 | * latest versions of your PIP requirements | ||
4780 | 93 | |||
4781 | 94 | The proper `PYTHONPATH` environment variable will also be set in the wrapper | ||
4782 | 95 | scripts generated by snapcraft or when running your app locally. | ||
4783 | 96 | |||
4784 | 97 | Python parts support standard snapcraft options and the requirements option | ||
4785 | 98 | to point PIP at its requirements file. | ||
4786 | 99 | |||
4787 | 100 | Why embed a Python runtime? While Snappy does currently include a Python | ||
4788 | 101 | runtime, this might not be the one you need, and it might be updated to a | ||
4789 | 102 | different version or removed in a Snappy update. This is why applications | ||
4790 | 103 | using Python should embed their copy of the Python runtime. | ||
4791 | 104 | |||
4792 | 105 | |||
4793 | 106 | ## Snapcraft for Java, Maven or Ant | ||
4794 | 107 | |||
4795 | 108 | Snapcraft includes support for building parts with Apache Maven or Ant; | ||
4796 | 109 | here's how a snapcraft.yaml parts section will look like: | ||
4797 | 110 | |||
4798 | 111 | parts: | ||
4799 | 112 | webapp: | ||
4800 | 113 | plugin: maven | ||
4801 | 114 | source: git://github.com/lool/snappy-mvn-demo.git | ||
4802 | 115 | |||
4803 | 116 | A Maven part will typically: | ||
4804 | 117 | |||
4805 | 118 | * make sure the tool is installed on the build host | ||
4806 | 119 | * embed a Java runtime in your snap | ||
4807 | 120 | * run `mvn package` and copy the resulting `*.jar` and `*.war` files in | ||
4808 | 121 | your snaps `jar/` and `war/` directories | ||
4809 | 122 | |||
4810 | 123 | An Ant part works similarly, except it runs ant and sets the proper | ||
4811 | 124 | `CLASSPATH` environment variable in the wrapper scripts generated by | ||
4812 | 125 | snapcraft or when running the app locally. | ||
4813 | 126 | |||
4814 | 127 | If you only need to embed a Java runtime, add a part with the jdk type. This | ||
4815 | 128 | will pull a relocatable OpenJDK via the default-jdk Ubuntu package and will | ||
4816 | 129 | set the proper `JAVA_HOME` and `PATH` environment variables in wrapper | ||
4817 | 130 | scripts generated by snapcraft or when running the app locally. | ||
4818 | 0 | 131 | ||
4819 | === added file 'md_importer/tests/data/snapcraft-test/docs/snapcraft-syntax.md' | |||
4820 | --- md_importer/tests/data/snapcraft-test/docs/snapcraft-syntax.md 1970-01-01 00:00:00 +0000 | |||
4821 | +++ md_importer/tests/data/snapcraft-test/docs/snapcraft-syntax.md 2016-01-19 00:21:38 +0000 | |||
4822 | @@ -0,0 +1,83 @@ | |||
4823 | 1 | # Syntax of the snapcraft.yaml file | ||
4824 | 2 | |||
4825 | 3 | The `snapcraft.yaml` file is the main entry point to create a snap through | ||
4826 | 4 | Snapcraft. The main building blocks are parts and each defined part is | ||
4827 | 5 | independent from each other. In addition to parts, there are attributes | ||
4828 | 6 | that define the metadata for the snap package. | ||
4829 | 7 | |||
4830 | 8 | What follows is a list of all the attributes the `snapcraft.yaml` file can | ||
4831 | 9 | contain. | ||
4832 | 10 | |||
4833 | 11 | * `name` (string) | ||
4834 | 12 | The name of the resulting snap. | ||
4835 | 13 | * `version` (string) | ||
4836 | 14 | The version of the resulting snap. | ||
4837 | 15 | * `summary` (string) | ||
4838 | 16 | A 78 character long summary for the snap. | ||
4839 | 17 | * `description` (string) | ||
4840 | 18 | The description for the snap, this can and is expected to be a longer | ||
4841 | 19 | explanation for the snap. | ||
4842 | 20 | * `config` (string) | ||
4843 | 21 | Path to the runnable in snap that will be used as the [Snappy config | ||
4844 | 22 | interface](https://developer.ubuntu.com/snappy/guides/config-command/). | ||
4845 | 23 | * `services` (yaml subsection) | ||
4846 | 24 | A set of keys representing service names with values as defined by the | ||
4847 | 25 | [Snappy packaging spec](https://developer.ubuntu.com/snappy/guides/packaging-format-apps/). | ||
4848 | 26 | * `binaries` (yaml subsection) | ||
4849 | 27 | A set of keys representing binary names with values as defined by the | ||
4850 | 28 | [Snappy packaging spec](https://developer.ubuntu.com/snappy/guides/packaging-format-apps/). | ||
4851 | 29 | * `icon` (string) | ||
4852 | 30 | Path to the icon that will be used for the snap. | ||
4853 | 31 | * `license` (yaml subsection) | ||
4854 | 32 | License the snap will carry. | ||
4855 | 33 | * `text` (string) | ||
4856 | 34 | The license text for the snap itself. | ||
4857 | 35 | * `accept-required` (boolean) | ||
4858 | 36 | If true, license acceptance is required for the package to be activated. | ||
4859 | 37 | A good example for this one is the Sun JRE/JDK being bundled in a snap. | ||
4860 | 38 | * `framework-policy` (string) | ||
4861 | 39 | A relative path to a directory containing additional policies, used if | ||
4862 | 40 | creating a framework and want to extend permissions to snap apps. | ||
4863 | 41 | * `parts` (yaml subsection) | ||
4864 | 42 | A map of part names to their own part configuration. Order in the file is | ||
4865 | 43 | not relevant (to aid copy-and-pasting). | ||
4866 | 44 | * `plugin` (string) | ||
4867 | 45 | Specifies the plugin name that will manage this part. Snapcraft will pass | ||
4868 | 46 | to it all the other user-specified part options. If plugin is not | ||
4869 | 47 | defined, [the wiki](https://wiki.ubuntu.com/Snappy/Parts) will be | ||
4870 | 48 | searched for the part, the local values defined in the part will be used | ||
4871 | 49 | to compose the final part. | ||
4872 | 50 | * `after` (list of strings) | ||
4873 | 51 | Specifies any parts that should be built before this part is. This is | ||
4874 | 52 | mostly useful when a part needs a library or build tool built by another | ||
4875 | 53 | part. If the part defined in after is not defined locally, the part will | ||
4876 | 54 | be searched for in [the wiki](https://wiki.ubuntu.com/Snappy/Parts). | ||
4877 | 55 | *If a part is supposed to run after another, the prerequisite part will | ||
4878 | 56 | be staged before the dependent part starts its lifecycle.* | ||
4879 | 57 | * `stage-packages` (list of strings) | ||
4880 | 58 | A list of Ubuntu packages to use that would support the part creation. | ||
4881 | 59 | * `filesets` (yaml subsection) | ||
4882 | 60 | A dictionary with filesets, the key being a recognizable user defined | ||
4883 | 61 | string and its value a list of strings of files to be included or | ||
4884 | 62 | excluded. Globbing is achieved with `*` for either inclusions or | ||
4885 | 63 | exclusion. Exclusions are denoted by a `-`. Globbing is computed from | ||
4886 | 64 | the private sections of the part. | ||
4887 | 65 | * `organize` (yaml subsection) | ||
4888 | 66 | A dictionary exposing replacements, the key is the internal name whilst | ||
4889 | 67 | the value the exposed name, filesets will refer to the exposed named | ||
4890 | 68 | applied after organization is applied. | ||
4891 | 69 | * `stage` (list of strings) | ||
4892 | 70 | A list of files from a part's installation to expose in stage. Rules | ||
4893 | 71 | applying to the list here are the same as those of filesets. Referencing | ||
4894 | 72 | of fileset keys is done with a $ prefixing the fileset key, which will | ||
4895 | 73 | expand with the value of such key. | ||
4896 | 74 | * `snap` (list of strings) | ||
4897 | 75 | A list of files from a part's installation to expose in snap. Rules | ||
4898 | 76 | applying to the list here are the same as those of filesets. Referencing | ||
4899 | 77 | of fileset keys is done with a `$` prefixing the fileset key, which will | ||
4900 | 78 | expand with the value of such key. | ||
4901 | 79 | |||
4902 | 80 | The `snapcraft.yaml` in any project is validated to be compliant to these | ||
4903 | 81 | keywords, if there is any missing expected component or invalid value, | ||
4904 | 82 | `snapcraft` will exit with an error. | ||
4905 | 83 | |||
4906 | 0 | 84 | ||
4907 | === added file 'md_importer/tests/data/snapcraft-test/docs/snapcraft-usage.md' | |||
4908 | --- md_importer/tests/data/snapcraft-test/docs/snapcraft-usage.md 1970-01-01 00:00:00 +0000 | |||
4909 | +++ md_importer/tests/data/snapcraft-test/docs/snapcraft-usage.md 2016-01-19 00:21:38 +0000 | |||
4910 | @@ -0,0 +1,43 @@ | |||
4911 | 1 | # Trying snapcraft | ||
4912 | 2 | |||
4913 | 3 | In the example directory, you can look at each individual `snapcraft.yaml` | ||
4914 | 4 | to see how each project is composed. Within each of these directories try | ||
4915 | 5 | running `snapcraft` to build a snap for each of these or go through the | ||
4916 | 6 | lifecycle by running: | ||
4917 | 7 | |||
4918 | 8 | $ snapcraft pull | ||
4919 | 9 | $ snapcraft build | ||
4920 | 10 | $ snapcraft stage | ||
4921 | 11 | $ snapcraft strip | ||
4922 | 12 | $ snapcraft snap | ||
4923 | 13 | |||
4924 | 14 | That sequence of commands basically went through the lifecycle of all the | ||
4925 | 15 | defined parts. To quickly inspect the parts a Snapcraft project has, open | ||
4926 | 16 | the `snapcraft.yaml` file of the corresponding example and look at the keys | ||
4927 | 17 | inside the parts entry. | ||
4928 | 18 | |||
4929 | 19 | |||
4930 | 20 | ## Sideloading your snap | ||
4931 | 21 | |||
4932 | 22 | Consider the `downloader-with-wiki-parts` example and a Snappy Ubuntu Core | ||
4933 | 23 | on 192.168.10.10, to install the built snap by following Trying snapcraft | ||
4934 | 24 | and run: | ||
4935 | 25 | |||
4936 | 26 | snappy-remote --url ssh://192.168.10.10 install downloader_1.0_amd64.snap | ||
4937 | 27 | |||
4938 | 28 | If this is the first time connecting to the system, snappy-remote will try | ||
4939 | 29 | and use existing ssh keys for the user to avoid the necessity of password | ||
4940 | 30 | prompts. | ||
4941 | 31 | |||
4942 | 32 | After installing a summary of installed snaps will be presented, on vanilla | ||
4943 | 33 | x86-64 bit system it would look a lot like this: | ||
4944 | 34 | |||
4945 | 35 | Name Date Version Developer | ||
4946 | 36 | ubuntu-core 2015-09-17 5 ubuntu | ||
4947 | 37 | downloader 2015-10-01 ICIEPfXHQOaC sideload | ||
4948 | 38 | generic-amd64 2015-10-01 1.4 canonical | ||
4949 | 39 | |||
4950 | 40 | Take notice of the sideload word in the downloader snap, this indicates that | ||
4951 | 41 | the snap did not come signed from the store, if an app is sideloaded, it | ||
4952 | 42 | also fakes the version to allow easy iteration without the need to change the | ||
4953 | 43 | metadata. | ||
4954 | 0 | 44 | ||
4955 | === added file 'md_importer/tests/data/snapcraft-test/docs/your-first-snap.md' | |||
4956 | --- md_importer/tests/data/snapcraft-test/docs/your-first-snap.md 1970-01-01 00:00:00 +0000 | |||
4957 | +++ md_importer/tests/data/snapcraft-test/docs/your-first-snap.md 2016-01-19 00:21:38 +0000 | |||
4958 | @@ -0,0 +1,318 @@ | |||
4959 | 1 | # Snapcraft Tutorial | ||
4960 | 2 | |||
4961 | 3 | Let's make a snap from scratch using Snapcraft! We'll pick something a little | ||
4962 | 4 | interesting: a webcam server. | ||
4963 | 5 | |||
4964 | 6 | ## Preparation | ||
4965 | 7 | |||
4966 | 8 | You'll want a webcam and a Snappy device. We'll assume you have those, | ||
4967 | 9 | but if you need help setting up a Snappy install, there is help | ||
4968 | 10 | [online](https://developer.ubuntu.com/en/snappy/start/). | ||
4969 | 11 | |||
4970 | 12 | (Even if you don't have either of those, you can still follow along. You just | ||
4971 | 13 | won't be able to use the final snap package you create. But you'll get to see | ||
4972 | 14 | how Snapcraft works, which is still super rewarding.) | ||
4973 | 15 | |||
4974 | 16 | ## Approach | ||
4975 | 17 | |||
4976 | 18 | This example is easy because we won't be doing much of the heavy lifting | ||
4977 | 19 | ourselves. We're going to integrate a couple pieces of code together to make | ||
4978 | 20 | an interesting app. | ||
4979 | 21 | |||
4980 | 22 | Namely, we'll combine a web server with a webcam program and combine | ||
4981 | 23 | them to serve a new frame every ten seconds. | ||
4982 | 24 | |||
4983 | 25 | > The resulting package is also part of the examples directory in the | ||
4984 | 26 | > [snapcraft sources](https://github.com/ubuntu-core/snapcraft/tree/master/examples/webcam-webui) | ||
4985 | 27 | |||
4986 | 28 | ### The Web Server | ||
4987 | 29 | |||
4988 | 30 | Go has a simple web server in its standard libraries. So let's just use that. | ||
4989 | 31 | |||
4990 | 32 | It's trivial to write a complete (but basic) web server in a few lines: | ||
4991 | 33 | |||
4992 | 34 | package main | ||
4993 | 35 | import "net/http" | ||
4994 | 36 | func main() { | ||
4995 | 37 | panic(http.ListenAndServe(":8080", http.FileServer(http.Dir(".")))) | ||
4996 | 38 | } | ||
4997 | 39 | |||
4998 | 40 | This will serve the current directory on port `:8080`. If there is an | ||
4999 | 41 | `index.html` in the current directory, it will be served. Otherwise a | ||
5000 | 42 | directory listing will be shown. |
The diff has been truncated for viewing.
Thanks Daniel, I'll start testing today