forked from r-lib/usethis
/
principles.html
282 lines (238 loc) · 16.9 KB
/
principles.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
<!-- Generated by pkgdown: do not edit by hand -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>usethis design principles • usethis</title>
<!-- favicons -->
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="apple-touch-icon.png" />
<link rel="apple-touch-icon" type="image/png" sizes="120x120" href="apple-touch-icon-120x120.png" />
<link rel="apple-touch-icon" type="image/png" sizes="76x76" href="apple-touch-icon-76x76.png" />
<link rel="apple-touch-icon" type="image/png" sizes="60x60" href="apple-touch-icon-60x60.png" />
<!-- jquery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha256-916EbMg70RQy9LHiGkXzG8hSg9EdNy97GazNG/aiY1w=" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha256-U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8=" crossorigin="anonymous"></script>
<!-- Font Awesome icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
<!-- clipboard.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.4/clipboard.min.js" integrity="sha256-FiZwavyI2V6+EXO1U+xzLG3IKldpiTFf3153ea9zikQ=" crossorigin="anonymous"></script>
<!-- sticky kit -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/sticky-kit/1.1.3/sticky-kit.min.js" integrity="sha256-c4Rlo1ZozqTPE2RLuvbusY3+SU1pQaJC0TjuhygMipw=" crossorigin="anonymous"></script>
<!-- pkgdown -->
<link href="pkgdown.css" rel="stylesheet">
<script src="pkgdown.js"></script>
<!-- docsearch -->
<script src="docsearch.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/docsearch.js/2.6.1/docsearch.min.css" integrity="sha256-QOSRU/ra9ActyXkIBbiIB144aDBdtvXBcNc3OTNuX/Q=" crossorigin="anonymous" />
<link href="docsearch.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/jquery.mark.min.js" integrity="sha256-4HLtjeVgH0eIB3aZ9mLYF6E8oU5chNdjU6p6rrXpl9U=" crossorigin="anonymous"></script>
<meta property="og:title" content="usethis design principles" />
<meta property="og:image" content="https://usethis.r-lib.org/logo.png" />
<meta name="twitter:card" content="summary" />
<meta name="robots" content="noindex">
<!-- mathjax -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js" integrity="sha256-nvJJv9wWKEm88qvoQl9ekL2J+k/RWIsaSScxxlsrv8k=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/config/TeX-AMS-MML_HTMLorMML.js" integrity="sha256-84DKXVJXs0/F8OTMzX4UR909+jtl4G7SPypPavF+GfA=" crossorigin="anonymous"></script>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-115082821-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-115082821-1');
</script>
</head>
<body>
<div class="container template-title-body">
<header>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<span class="navbar-brand">
<a class="navbar-link" href="index.html">usethis</a>
<span class="version label label-danger" data-toggle="tooltip" data-placement="bottom" title="In-development version">1.5.0.9000</span>
</span>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>
<a href="index.html">
<span class="fa fa-home fa-lg"></span>
</a>
</li>
<li>
<a href="articles/articles/usethis-setup.html">Setup</a>
</li>
<li>
<a href="reference/index.html">Reference</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
News
<span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu">
<li class="dropdown-header">Blog posts</li>
<li>
<a href="https://www.tidyverse.org/articles/2019/04/usethis-1.5.0/">usethis 1.5.0 (and 1.4.0)</a>
</li>
<li>
<a href="https://www.tidyverse.org/articles/2018/02/usethis-1-3-0/">usethis 1.3.0</a>
</li>
<li>
<a href="https://www.tidyverse.org/articles/2017/11/usethis-1.0.0/">usethis 1.0.0 (and 1.1.0)</a>
</li>
<li class="divider"></li>
<li>
<a href="news/index.html">Change log</a>
</li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="https://github.com/r-lib/usethis">
<span class="fa fa-github fa-lg"></span>
</a>
</li>
</ul>
<form class="navbar-form navbar-right" role="search">
<div class="form-group">
<input type="search" class="form-control" name="search-input" id="search-input" placeholder="Search..." aria-label="Search for..." autocomplete="off">
</div>
</form>
</div><!--/.nav-collapse -->
</div><!--/.container -->
</div><!--/.navbar -->
</header>
<div class="row">
<div class="contents col-md-9">
<div class="page-header">
<h1>usethis design principles</h1>
</div>
<div id="usethis-design-principles" class="section level1">
<p><em>This is an experiment in making key package design principles explicit, versus only implicit in the code. The goal is to make maintenance easier, when spread out over time and across people.</em></p>
<div id="active-project" class="section level2">
<h2 class="hasAnchor">
<a href="#active-project" class="anchor"></a>Active project</h2>
<p>Many usethis functions act on the <strong>active project</strong>, the path to which is stored in the internal environment <code>proj</code>, specifically in <code>proj$cur</code>. We do this instead of constantly passing around a base path or relying on the working directory. It is implied that such functions create or modify files inside the active project. This is mostly true of <code>use_*()</code> functions, though there are exceptions. For example, <code><a href="reference/zip-utils.html">use_course()</a></code> makes no reference to the active project.</p>
<p>The project is activated upon first need, i.e. eventually some function calls <code><a href="reference/proj_utils.html">proj_get()</a></code> and, if <code>proj$cur</code> is <code>NULL</code>, we attempt to activate a project at (or above) current working directory.</p>
<p>Direct read/write of <code>proj$cur</code> should be rare. Even internally, <code><a href="reference/proj_utils.html">proj_get()</a></code> and <code><a href="reference/proj_utils.html">proj_set()</a></code> are preferred. The stored project path should be processed with <code>proj_path_prep()</code>.</p>
<p>Form paths to files within the project with <code><a href="reference/proj_utils.html">proj_path()</a></code>. Get paths relative to the project with <code>proj_rel_path()</code>.</p>
<div id="activation-upon-load-or-attach-no-" class="section level3">
<h3 class="hasAnchor">
<a href="#activation-upon-load-or-attach-no-" class="anchor"></a>Activation upon load or attach? No.</h3>
<p>We’ve contemplated project activation in <code><a href="https://www.rdocumentation.org/packages/base/topics/ns-hooks">.onLoad()</a></code> or <code><a href="https://www.rdocumentation.org/packages/base/topics/ns-hooks">.onAttach()</a></code>, but it’s not clear which is more appropriate. Which suggests that neither is appropriate. If we ever do this, <code>zzz.R</code> would include something like this:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode r"><code class="sourceCode r"><a class="sourceLine" id="cb1-1" data-line-number="1">.onLoad <-<span class="st"> </span><span class="cf">function</span>(libname, pkgname) {</a>
<a class="sourceLine" id="cb1-2" data-line-number="2"> <span class="kw"><a href="https://www.rdocumentation.org/packages/base/topics/try">try</a></span>(<span class="kw"><a href="reference/proj_utils.html">proj_set</a></span>(<span class="st">"."</span>, <span class="dt">quiet =</span> <span class="ot">TRUE</span>), <span class="dt">silent =</span> <span class="ot">TRUE</span>)</a>
<a class="sourceLine" id="cb1-3" data-line-number="3">}</a></code></pre></div>
<p>Why not <code><a href="https://www.rdocumentation.org/packages/base/topics/ns-hooks">.onAttach()</a></code>?</p>
<ul>
<li>A package that imported usethis would also need to set project on attach.</li>
<li>Currently user can open R, attach (or load) usethis, and then change working directory to their target project. As long as they are in the project the first time <code><a href="reference/proj_utils.html">proj_get()</a></code> is called, the correct project will be made active. This is not our preferred workflow, but it is common.</li>
</ul>
</div>
</div>
<div id="helper-functions" class="section level2">
<h2 class="hasAnchor">
<a href="#helper-functions" class="anchor"></a>Helper functions</h2>
<p>With some ambivalence, internally-oriented helpers like <code><a href="reference/write-this.html">write_union()</a></code> are exported. This helps developers who are extending usethis to create a package to standardize project setup within their own organization.</p>
<p>The downside is that we aren’t exactly sure yet what we’re willing to guarantee about these helpers.</p>
<div id="permission-to-overwrite" class="section level3">
<h3 class="hasAnchor">
<a href="#permission-to-overwrite" class="anchor"></a>Permission to overwrite</h3>
<p><code><a href="reference/write-this.html">write_over()</a></code> returns <code>FALSE</code> and does nothing if there is no need to overwrite (proposed file contents are same as existing) or if user says “no” and returns <code>TRUE</code> if it ovewrites. So if downstream logic depends on whether something new was written, consult the return value. <code><a href="reference/write-this.html">write_over()</a></code> is rarely called directly, but is usually called via <code><a href="reference/use_template.html">use_template()</a></code>, in which case the same handling should apply to its return value.</p>
</div>
<div id="helpers-and-the-active-project" class="section level3">
<h3 class="hasAnchor">
<a href="#helpers-and-the-active-project" class="anchor"></a>Helpers and the active project</h3>
<p>Current mindset: helpers should <em>not</em> make direct use of the active project, i.e. project-based paths should be formed by the caller.</p>
<p>Uncomfortable fact: <code><a href="reference/write-this.html">write_union()</a></code> uses the active project, if such exists, to create a humane path in its message. However, unlike <code>use_*()</code> functions, it does not call <code><a href="reference/proj_utils.html">proj_get()</a></code> to set an active project when <code>proj$cur</code> is <code>NULL</code>. We like this behaviour but the design feels muddy.</p>
</div>
</div>
<div id="home-directory" class="section level2">
<h2 class="hasAnchor">
<a href="#home-directory" class="anchor"></a>Home directory</h2>
<p>usethis relies on fs for file system operations. The main thing users will notice is the treatment of home directory on Windows. A Windows user’s home directory is interpreted as <code>C:\Users\username</code> (typical of Unix-oriented tools, like Git and ssh; also matches Python), as opposed to <code>C:\Users\username\Documents</code> (R’s default on Windows). In order to be consistent everywhere, all paths supplied by the user should be processed with <code>user_path_prep()</code>.</p>
</div>
<div id="communicating-with-the-user" class="section level2">
<h2 class="hasAnchor">
<a href="#communicating-with-the-user" class="anchor"></a>Communicating with the user</h2>
<p>User-facing messages are emitted via helpers in <code>style.R</code> (see <code><a href="reference/ui.html">ui_todo()</a></code> and <code><a href="reference/ui.html">ui_done()</a></code>) and <em>everything</em> is eventually routed through <code>cat_line()</code>. This is all intentional and should be preserved.</p>
<p><code>cat_line()</code> has a <code>quiet</code> argument and <code>quiet = TRUE</code> causes it to not produce output. Default value: <code>quiet = getOption("usethis.quiet", default = FALSE)</code>.</p>
<ul>
<li>Exploited in usethis tests: option is set and unset in <code>setup.R</code> and <code>teardown.R</code>. Eliminates the need for ubiquitous <code>capture_output()</code> calls.</li>
<li>Other packages can muffle a usethis call via, e.g., <code><a href="https://www.rdocumentation.org/packages/withr/topics/with_options">withr::local_options(list(usethis.quiet = TRUE))</a></code>.</li>
</ul>
<p>Implication: don’t call <code>cat_line(..., quiet = FALSE)</code> lightly, because it breaks the expectation that the option can be used to silence usethis.</p>
<p>You might also notice that usethis communicates with the user via <code><a href="https://www.rdocumentation.org/packages/base/topics/cat">cat()</a></code> instead of <code><a href="https://www.rdocumentation.org/packages/base/topics/message">message()</a></code>. Why?</p>
<ul>
<li>Pragmatic explanation: default styling of <code><a href="https://www.rdocumentation.org/packages/base/topics/message">message()</a></code> (at least in RStudio) is red, which suggests that something is wrong. We prefer default styling to be more neutral and less alarmist.</li>
<li>Principled explanation: if one diverts where various streams go, <code><a href="https://www.rdocumentation.org/packages/base/topics/cat">cat()</a></code> follows printed output, whereas <code><a href="https://www.rdocumentation.org/packages/base/topics/message">message()</a></code> goes to standard error.</li>
</ul>
</div>
<div id="git" class="section level2">
<h2 class="hasAnchor">
<a href="#git" class="anchor"></a>Git</h2>
<p>We make a strong assumption that user follows these conventions for branch and remote names:</p>
<ul>
<li>
<code>master</code> is the main default branch.</li>
<li>If you’ve got only one remote, it’s called <code>origin</code>.</li>
<li>If you’ve got multiple remotes, one of them is <code>origin</code> and it is the main default remote.</li>
<li>If you’ve forked something, you have at least two remotes:
<ul>
<li>
<code>origin</code> is your copy.</li>
<li>
<code>upstream</code> is the original repo you forked.</li>
</ul>
</li>
</ul>
<p>We assume a user habitually uses one transport protocol, either SSH or HTTPS, i.e. that they don’t intentionally switch between them willy-nilly.</p>
<p>If the default summoning of Git credentials or protocol or GitHub PAT doesn’t work for you, you must set them explicitly via <code><a href="reference/git_credentials.html">use_git_credentials()</a></code>, <code><a href="reference/git_protocol.html">use_git_protocol()</a></code>, or <code><a href="https://www.rdocumentation.org/packages/base/topics/Sys.setenv">Sys.setenv("GITHUB_PAT")</a></code>. We aren’t going to offer fine control of this, everywhere, by repetitively offering a ton of function arguments.</p>
</div>
</div>
</div>
</div>
<footer>
<div class="copyright">
<p>Developed by <a href='http://hadley.nz'>Hadley Wickham</a>, <a href='https://jennybryan.org'>Jennifer Bryan</a>, <a href='https://www.rstudio.com'><img src='https://www.tidyverse.org/rstudio-logo.svg' alt='RStudio' height='24' /></a>.</p>
</div>
<div class="pkgdown">
<p>Site built with <a href="https://pkgdown.r-lib.org/">pkgdown</a> 1.3.0.</p>
</div>
</footer>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/docsearch.js/2.6.1/docsearch.min.js" integrity="sha256-GKvGqXDznoRYHCwKXGnuchvKSwmx9SRMrZOTh2g4Sb0=" crossorigin="anonymous"></script>
<script>
docsearch({
apiKey: 'f67d4c46004acc8326e8d95f296a680e',
indexName: 'usethis',
inputSelector: 'input#search-input.form-control',
transformData: function(hits) {
return hits.map(function (hit) {
hit.url = updateHitURL(hit);
return hit;
});
}
});
</script>
</body>
</html>