]> Sergey Matveev's repositories - pyimportcan.git/blob - pyimportcan.pl
2641c4f295eb2db009b378a83a72b30c6a33d0df
[pyimportcan.git] / pyimportcan.pl
1 #!/usr/bin/env perl
2 # pyimportcan.pl -- Python imports canonical format filter
3 # Copyright (C) 2013-2018 Sergey Matveev (stargrave@stargrave.org)
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 =pod
18
19 =head1 PROBLEM
20
21 Python allows various different formats of how to make importing of
22 modules and functions:
23
24     from foo import bar
25
26     from foo import bar, baz
27
28     from foo import (
29       bar,
30       baz
31     )
32
33     from foo import (
34       baz,
35       bar,
36     )
37
38     from foo import bar, \
39         baz
40
41 Everything can be unsorted at any place, thus making the task of
42 checking if import already exists rather hard. Moreover it complicates
43 just adding another import, because you have to find it first and deal
44 with the format it is already written.
45
46 Everything can use different indentation practices. Everything can be
47 doubled and you won't notice it:
48
49     from foo import bar
50     [many lines]
51     from foo import (
52         baz,
53         bar
54     )
55
56 Allowing multiple imports on one line leads to possibility of long lines
57 appearing.
58
59 The whole import sections with various formats just can not be
60 technically sorted with the editor (no way to do "vip:sort<CR>" in Vim).
61
62 Merge conflict in the imports section can be not so trivial to resolve,
63 however in most cases just aggregating them will be enough.
64
65 If you want to find the code that imports bar from foo, then you can do
66 it with "from foo import bar", but can not with "import (\n" is most
67 cases. You are forced to use special Python-related software to deal
68 with it, but no grep or sed.
69
70 =head1 SOLUTION
71
72 Use the very convenient simple single format. Single import per line,
73 period. Sorted and only identical ones. With git merge conflict lines
74 removed. This script reads stdin with import-lines and writes canonical
75 representation of them to stdout. For example you can select lines in
76 Vim and just enter ":!pyimportcan.pl".
77
78 From that kind of imports:
79
80     from waaccounts.models import OrgRole
81     import math
82     from waaccounts.models import UserAccount
83     from waadv.filters import AdvCampaignFilterSet as foo1
84     from waadv.filters import AdvCampaignBidFilterSet
85     from waadv.filters import AdvCampaignUniqueShowFilterSet as foo2
86     from waadv.filters import VasttaskFilterSet
87     from waadv.forms import AdvCampaignCreateForm
88     from waadv.forms import PriceListTotalBudgetDiscountFormset
89     from waadv.models import Adv
90     from waadv.models import PriceListTotalBudgetDiscount
91     from waadv.models import VastTask
92     from wacontent.models import VrContractGroup
93     from wacatalog.models import AdvGroup
94     from wahelpers.counters import adv_campaign_click_counter_loader
95     from wahelpers.counters import adv_video_view_fact_counter_loader
96     from wahelpers.counters import adv_view_percent
97     from wahelpers.counters import ctr
98     from wahelpers.functions import get_json_rpc_proxy, get_return_url
99     <<<<<<< HEAD
100     from waorigin.kendo_menus.toolbar import KendoGridToolbar
101     from waorigin.kendo_menus.toolbar_button import KendoToolbarButton
102     =======
103     from http_utils import JSONResponse
104     from wahelpers.counters import (
105         adv_campaign_click_counter_loader,
106         adv_campaign_click_fact_counter_loader,
107         ctr,
108         adv_view_percent,
109     )
110     from waorigin.kendo_menus.toolbar_button import (
111     AddKendoToolbarButton,
112     StatusKendoToolbarButton
113     )
114     from waorigin.models import Content, ContentCategory3 as foo4, ContentCategory2, Files
115     from waorigin.views import filter_view, model_autocompletion, \
116         create_update_with_auto_user, get_objects_for_edit_many
117     from wapartners.models import PrNomenclature, PrSite
118     >>>>>>> 1bf0b64... fixed view refs #11992
119     from waorigin.kendo_data_source import KendoDataSourceView
120     from waorigin.models import Content
121     from waorigin.models import ContentCategory2
122     from waorigin.models import ContentCategory3
123     from waorigin.models import Files
124     from waorigin.views import create_update_with_auto_user
125     from waorigin.views import filter_view
126     from waorigin.views import get_objects_for_edit_many
127     from waorigin.views import model_autocompletion
128     from wapartners.models import PrSite
129     from wapartners.models import PrNomenclature
130
131 it makes the following one (by feeding it to stdin and capturing on
132 stdout):
133
134     import math
135     from http_utils import JSONResponse
136     from waaccounts.models import OrgRole
137     from waaccounts.models import UserAccount
138     from waadv.filters import AdvCampaignBidFilterSet
139     from waadv.filters import AdvCampaignFilterSet as foo1
140     from waadv.filters import AdvCampaignUniqueShowFilterSet as foo2
141     from waadv.filters import VasttaskFilterSet
142     from waadv.forms import AdvCampaignCreateForm
143     from waadv.forms import PriceListTotalBudgetDiscountFormset
144     from waadv.models import Adv
145     from waadv.models import PriceListTotalBudgetDiscount
146     from waadv.models import VastTask
147     from wacatalog.models import AdvGroup
148     from wacontent.models import VrContractGroup
149     from wahelpers.counters import adv_campaign_click_counter_loader
150     from wahelpers.counters import adv_campaign_click_fact_counter_loader
151     from wahelpers.counters import adv_video_view_fact_counter_loader
152     from wahelpers.counters import adv_view_percent
153     from wahelpers.counters import ctr
154     from wahelpers.functions import get_json_rpc_proxy
155     from wahelpers.functions import get_return_url
156     from waorigin.kendo_data_source import KendoDataSourceView
157     from waorigin.kendo_menus.toolbar import KendoGridToolbar
158     from waorigin.kendo_menus.toolbar_button import AddKendoToolbarButton
159     from waorigin.kendo_menus.toolbar_button import KendoToolbarButton
160     from waorigin.kendo_menus.toolbar_button import StatusKendoToolbarButton
161     from waorigin.models import Content
162     from waorigin.models import ContentCategory2
163     from waorigin.models import ContentCategory3
164     from waorigin.models import ContentCategory3 as foo4
165     from waorigin.models import Files
166     from waorigin.views import create_update_with_auto_user
167     from waorigin.views import filter_view
168     from waorigin.views import get_objects_for_edit_many
169     from waorigin.views import model_autocompletion
170     from wapartners.models import PrNomenclature
171     from wapartners.models import PrSite
172
173 =head1 AUTHOR
174
175 Sergey Matveev L<mailto:stargrave@stargrave.org>
176
177 =cut
178
179 use strict;
180 use warnings;
181
182 my $buf;
183 my $con;
184 my @imports;
185 my %parsed;
186
187 # Collect strings and aggregate the splitted ones
188 while(<>){
189     next if /^[<=>]{2,}/;
190     next if /^\s*#/;
191     chop;
192     $buf = ($con ? $buf : "") . $_;
193     $con = /[\\,\(]\s*$/ ? 1 : 0;
194     next if /^\s*\)*\s*$/;
195     push @imports, $buf;
196 };
197
198 # Consolidate information from where what is imported
199 foreach (@imports) {
200     s/[\\\(\)]//g;
201     s/  */ /g;
202     next if /import\s*$/;
203     /^(.*)\s*import\s*(.*)$/;
204     my ($where, $what) = ($1, $2);
205     map { $parsed{$where}->{$_}++ } split /\s*,\s*/, $what;
206 };
207
208 foreach my $where (sort keys %parsed){
209     map { print $where . "import $_\n" } sort keys %{$parsed{$where}};
210 };