-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add support for package-provided project templates #908
Conversation
@@ -138,6 +138,19 @@ std::pair<std::vector<std::string>, InputIterator> parseCsvLine( | |||
std::vector<std::string>(), begin); | |||
} | |||
|
|||
template <typename InputIterator> | |||
InputIterator parseCsvLine(InputIterator begin, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this overload primarily because the main version that returns a std::pair<>
can often be cumbersome to work with.
LOG_ERROR(error); | ||
|
||
core::json::fillVectorString( | ||
object["fields"].get_array(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC this will throw if the object does not contain fields
, and also if fields
is not an array, so you might consider a two-stage access here for safety (get the JSON array above then coerce to a string vector here)
"label", &ptwd.label); | ||
|
||
if (error) | ||
LOG_ERROR(error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it'd be better here to return the error (and take a pointer to a ptwd as a parameter) so that the caller knows not to process the template if it's malformed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea -- I'll make that change.
else if (key == "Icon") | ||
{ | ||
// read icon file from disk | ||
core::FilePath iconPath = resourcePath.parent().complete(value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be worth defending against pathologically large files here by skipping icons larger than some reasonable size (we don't want to accidentally try to stream megabytes of base64 data to the client).
if (error) | ||
LOG_ERROR(error); | ||
|
||
core::json::Array widgetsJson = object["widgets"].get_array(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above w/ fields
(this can throw exceptions)
private: | ||
void onIndexingStarted() | ||
{ | ||
pRegistry_ = boost::make_shared<ProjectTemplateRegistry>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this shared pointer manages the lifetime of the globalProjectTemplateRegistry
. If that's the case then does resetting it cause the pointer to release the registry, so that there is briefly no registry until indexing completes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not quite -- the indexer actually contains its own registry (and maintains a shared pointer to that); after indexing has completed the global registry is updated based on that reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the object ever actually get copied, though? If not the global registry is a reference to an object on the heap that's owned only by the shared pointer. Let's discuss realtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After discussion -- indeed you're right! The global registry needs to be represented as a shared pointer; in addition I'm missing a noncopyable
inheritance on the registry class. Thanks for pointing this out!
return Success(); | ||
|
||
std::string reason = | ||
"invalid project template description: missing or empty fields " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we also log the location of the file in which these fields were expected? You can just add this as a property on the error; otherwise I think the message in the logs will be hard to associate with a particular package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea!
pDescription->subtitle = "Create a new " + title; | ||
|
||
if (pDescription->subtitle.empty()) | ||
pDescription->caption = "Create " + title; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: dead code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops! This should be checking the caption
field; thanks for catching this.
I think many project skeletons are going to wind up including sample files with boilerplate code. For some projects it will be necessary to run R code to generate these, but I think most can just include them declaratively. We might want to add facilities for:
Also worth considering:
|
I'm slightly more inclined to delegate this responsibility to the skeleton function; any reason in particular why we should take control here?
Definitely a good idea!
I was also thinking of parsing project templates in a preset folder, e.g.
Yes, I think the Shiny package skeleton we have could be replaced with a pure project template version. However, note that currently all of these skeleton functions create a brand new project; the 'New Shiny Application' dialog merely creates some new files (ie it's less heavyweight). |
Some top-level comments prior to code review:
|
Whoops, yes -- sorry, I flipped the order! |
Some thoughts on how the user might specify what files should be opened when the new project is opened:
Ie, the
One thing that would be nice about an environment-variable based substitution -- the skeleton function could just set some environment variables that would could then be read by RStudio when forming the template file paths. We could also consider supporting globs, e.g.
|
I'd say support globs and support shell-style variables. Variables should On Tue, Nov 22, 2016 at 2:09 PM, Kevin Ushey notifications@github.com
|
} | ||
|
||
template <typename T> | ||
core::Error readObject( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method (+ the one above) allow us to more easily read vectors of things, e.g. vectors of strings.
I think this PR is ready for a more formal code review now. @jmcphers, if you have time would you be willing to take a look? |
else | ||
// if we have a custom project template, call that first | ||
if (!projectTemplateOptions.is_null()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for sanity, consider making sure its type is json::Object
here too (otherwise it looks like initializeProjectFromTemplate
can throw)
|
||
// execute pending callbacks | ||
for (Callback callback : pendingCallbacks_) | ||
callback.execute(registry_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Could double evaluate if a callback throws while executing -- maybe wrap in a try catch and/or remove the callback before executing?
@jjallaire, do you want to review / sanity check before merge? (We can also tweak + update things on master as needed) |
I'm good, will merge now.
…On Wed, Dec 21, 2016 at 1:03 PM, Kevin Ushey ***@***.***> wrote:
@jjallaire <https://github.com/jjallaire>, do you want to review / sanity
check before merge? (We can also tweak + update things on master as needed)
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#908 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAGXx1j6vI7NfAb4n14TStO8F2FYul0Qks5rKWnigaJpZM4K4ydg>
.
|
NOTE: PR not quite ready for merge, but most components ready for review.
This PR implements support for package-provided project templates. This PR basically provides an interface for RStudio to discover and call various 'skeleton' functions that packages can provide. A brief look:
Packages can define a project template by creating one or more skeleton descriptions in the
rstudio/templates
folder. The project template descriptions should be DCF files with the form e.g.That is, each DCF file should contain an initial DCF record containing information about the skeleton function, and then 0 or more descriptions of widgets that can be used to power the widget.
TODO
dcf
file in therstudio/templates
folder)Subtitle
,Caption
based onTitle
field.~/.R/rstudio/templates
directory?...