ci: do not error when nothing with nitunit_some
[nit.git] / lib / github / wallet.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Github OAuth tokens management
18 #
19 # When using batch mode with the `github` API, we can rapidly reach the rate
20 # limit allowed by Github.
21 #
22 # One solution consists in using a wallet of tokens so we can rely on more than
23 # one token and switch them when one become exhausted.
24 #
25 # ## Using the Github wallet to check tokens
26 #
27 # One functionality of the wallet is to check the validity of a token against
28 # the API. `check_token` will return false if a token is invalid or exhausted.
29 #
30 # ~~~
31 # var wallet = new GithubWallet
32 # assert not wallet.check_token("this is a bad token")
33 # ~~~
34 #
35 # ## Storing tokens
36 #
37 # The wallet can also be used to store tokens and check all of them.
38 #
39 # ~~~
40 # wallet = new GithubWallet
41 # wallet.add "some token"
42 # wallet.add "some other token"
43 # ~~~
44 #
45 # or
46 #
47 # ~~~
48 # wallet = new GithubWallet.from_tokens(["token 1", "token 2"])
49 # ~~~
50 #
51 # The `show_status` method can be used to display a summary of the validity of
52 # each token in the wallet.
53 #
54 # ~~~
55 # wallet.show_status
56 # ~~~
57 #
58 # Will display something like this:
59 #
60 # ~~~raw
61 # Wallet (2 tokens):
62 # * [OK] token 1
63 # * [KO] token 2
64 # ~~~
65 #
66 # ## Using the wallet to obtain a Github API client
67 #
68 # Using the wallet you can cycle through tokens and obtain a new Github API client
69 # instance with a fresh rate limit.
70 #
71 # ~~~
72 # wallet = new GithubWallet.from_tokens(["token 1", "token 2"])
73 # var api = wallet.api
74 # ~~~
75 #
76 # The wallet will automatically cycle through the registered tokens to find one
77 # that works.
78 #
79 # If no valid token is found after all of them was tried, the wallet returns a
80 # client based on the last tried one.
81 module wallet
82
83 import github
84 import console
85
86 # Github OAuth tokens wallet
87 class GithubWallet
88
89 # Github API tokens
90 var tokens = new Array[String]
91
92 # Init `self` from a collection of tokens
93 init from_tokens(tokens: Collection[String]) do self.tokens.add_all tokens
94
95 # Do not use colors in console output
96 var no_colors = false is writable
97
98 # Display debug information about the token processing
99 var verbose = false is writable
100
101 # Add a new token in the wallet
102 fun add(token: String) do tokens.add token
103
104 # Get an instance of GithubAPI based on the next available token.
105 #
106 # If no token is found, return an api based on the last exhausted token.
107 fun api: GithubAPI do
108 var token
109 if tokens.is_empty then
110 message "No tokens, using `get_github_oauth`"
111 token = get_github_oauth
112 else
113 token = get_next_token
114 var tried = 0
115 while not check_token(token) do
116 if tried >= tokens.length - 1 then
117 message "Exhausted all tokens, using {token}"
118 break
119 end
120 tried += 1
121 token = get_next_token
122 end
123 end
124 var api = new GithubAPI(token)
125 api.enable_cache = true
126 return api
127 end
128
129 # The current index in the `tokens` array
130 private var current_index = 0
131
132 # The current token in the `tokens` array based on `current_index`
133 fun current_token: String do return tokens[current_index]
134
135 # Get the next token in token `array` based on `current_token`.
136 #
137 # If the end of the list is reached, start again from the begining.
138 fun get_next_token: String do
139 if tokens.is_empty then
140 return get_github_oauth
141 end
142 var token = current_token
143
144 if current_index < tokens.length - 1 then
145 current_index += 1
146 else
147 current_index = 0
148 end
149 return token
150 end
151
152 # Check if a token is valid
153 fun check_token(token: String): Bool do
154 message "Try token {token}"
155 var api = new GithubAPI(token)
156 api.load_repo("nitlang/nit")
157 return not api.was_error
158 end
159
160 # Print a message depending on `verbose`
161 fun message(message: String) do if verbose then print "[Github Wallet] {message}"
162
163 # Show wallet status in console
164 fun show_status do
165 if tokens.is_empty then
166 print "Wallet is empty"
167 return
168 end
169 print "Wallet ({tokens.length} tokens):"
170 for token in tokens do
171 var status
172 if check_token(token) then
173 status = if no_colors then "OK" else "OK".green
174 else
175 status = if no_colors then "KO" else "KO".red
176 end
177 print " * [{status}] {token}"
178 end
179 end
180 end